import React from "react"; import { shallow, mount } from "enzyme"; import { ContentTiles } from "content-src/components/ContentTiles"; import { ActionChecklist } from "content-src/components/ActionChecklist"; import { MobileDownloads } from "content-src/components/MobileDownloads"; import { EmbeddedBackupRestore } from "content-src/components/EmbeddedBackupRestore"; import { EmbeddedMigrationWizard } from "content-src/components/EmbeddedMigrationWizard"; import { AboutWelcomeUtils } from "content-src/lib/aboutwelcome-utils.mjs"; import { GlobalOverrider } from "asrouter/tests/unit/utils"; describe("ContentTiles component", () => { let sandbox; let wrapper; let handleAction; let setActiveMultiSelect; let setActiveSingleSelectSelection; let globals; const CHECKLIST_TILE = { type: "action_checklist", header: { title: "Checklist Header", subtitle: "Checklist Subtitle", style: { border: "1px solid #ccc", }, }, data: [ { id: "action-checklist-test", targeting: "false", label: { raw: "Test label", }, action: { data: { pref: { name: "messaging-system-action.test1", value: "false", }, }, type: "SET_PREF", }, }, ], }; const MOBILE_TILE = { type: "mobile_downloads", header: { title: "Mobile Header", style: { backgroundColor: "#e0e0e0", border: "1px solid #999", }, }, data: { email: { link_text: "Email yourself a link", }, }, }; const TITLE_TILE = { type: "mobile_downloads", title: "Tile Title", subtitle: "Tile Subtitle", data: { email: { link_text: "Email yourself a link", }, }, }; const TEST_CONTENT = { tiles: [CHECKLIST_TILE, MOBILE_TILE], }; const EMBEDDED_BACKUP_RESTORE_TILE = { type: "backup_restore", title: "Tile Title", subtitle: "Tile Subtitle", }; beforeEach(() => { sandbox = sinon.createSandbox(); handleAction = sandbox.stub(); setActiveMultiSelect = sandbox.stub(); setActiveSingleSelectSelection = sandbox.stub(); globals = new GlobalOverrider(); globals.set({ AWSendToDeviceEmailsSupported: () => Promise.resolve(), }); globals.set({ AWSendToParent: sandbox.stub() }); globals.set({ AWSendToParent: sandbox.stub(), AWFindBackupsInWellKnownLocations: sandbox.stub().resolves({ found: false, multipleBackupsFound: false, backupFileToRestore: null, }), }); wrapper = shallow( ); }); afterEach(() => { sandbox.restore(); globals.restore(); }); it("should render the component when tiles are provided", () => { assert.ok(wrapper.exists()); }); it("should not render the component when no tiles are provided", () => { wrapper.setProps({ content: {} }); assert.ok(wrapper.isEmptyRender()); }); it("should render the correct number of tiles", () => { assert.equal(wrapper.find(".content-tile").length, 2); }); it("should toggle a tile and send telemetry when its header is clicked", () => { let telemetrySpy = sandbox.spy(AboutWelcomeUtils, "sendActionTelemetry"); const [firstTile] = TEST_CONTENT.tiles; const tileId = `${firstTile.type}${firstTile.id ? "_" : ""}${ firstTile.id ?? "" }_header`; const firstTileButton = wrapper.find(".tile-header").at(0); firstTileButton.simulate("click"); assert.equal( wrapper.find(".tile-content").at(0).prop("id"), "tile-content-0" ); assert.equal(wrapper.find(".tile-content").at(0).exists(), true); assert.calledOnce(telemetrySpy); assert.equal(telemetrySpy.firstCall.args[1], tileId); }); it("should only expand one tile at a time", () => { const firstTileButton = wrapper.find(".tile-header").at(0); firstTileButton.simulate("click"); const secondTileButton = wrapper.find(".tile-header").at(1); secondTileButton.simulate("click"); assert.equal(wrapper.find(".tile-content").length, 1); assert.equal( wrapper.find(".tile-content").at(0).prop("id"), "tile-content-1" ); }); it("should toggle all tiles and send telemetry when the tiles header is clicked", () => { const TEST_CONTENT_HEADER = { tiles: [CHECKLIST_TILE, MOBILE_TILE], tiles_header: { title: "Toggle Tiles Header", }, }; wrapper = mount( ); let telemetrySpy = sandbox.spy(AboutWelcomeUtils, "sendActionTelemetry"); const tilesHeaderButton = wrapper.find(".content-tiles-header"); assert.ok(tilesHeaderButton.exists(), "Tiles header button should exist"); tilesHeaderButton.simulate("click"); assert.equal( wrapper.find("#content-tiles-container").exists(), true, "Content tiles container should be visible after toggle" ); assert.calledOnce(telemetrySpy); assert.equal( telemetrySpy.firstCall.args[1], "content_tiles_header", "Telemetry should be sent for tiles header toggle" ); tilesHeaderButton.simulate("click"); assert.equal( wrapper.find("#content-tiles-container").exists(), false, "Content tiles container should not be visible after second toggle" ); }); it("should apply configured styles to the header buttons", () => { const mountedWrapper = mount( {}} activeMultiSelect={null} setActiveMultiSelect={setActiveMultiSelect} /> ); const firstTileHeader = mountedWrapper .find(".tile-header") .at(0) .getDOMNode(); const secondTileHeader = mountedWrapper .find(".tile-header") .at(1) .getDOMNode(); assert.equal( firstTileHeader.style.cssText, "border: 1px solid rgb(204, 204, 204);", "First tile header styles should match configured values" ); assert.equal( secondTileHeader.style.cssText, "background-color: rgb(224, 224, 224); border: 1px solid rgb(153, 153, 153);", "Second tile header styles should match configured values" ); mountedWrapper.unmount(); }); it("should render ActionChecklist for 'action_checklist' tile type", () => { const firstTileButton = wrapper.find(".tile-header").at(0); assert.ok(firstTileButton.exists(), "Tile header button should exist"); firstTileButton.simulate("click"); const actionChecklist = wrapper.find(ActionChecklist); assert.ok(actionChecklist.exists()); assert.deepEqual(actionChecklist.prop("content").tiles[0].data, [ { id: "action-checklist-test", targeting: "false", label: { raw: "Test label", }, action: { data: { pref: { name: "messaging-system-action.test1", value: "false", }, }, type: "SET_PREF", }, }, ]); }); it("should render MobileDownloads for 'mobile_downloads' tile type", () => { const secondTileButton = wrapper.find(".tile-header").at(1); assert.ok(secondTileButton.exists(), "Tile header button should exist"); secondTileButton.simulate("click"); const mobileDownloads = wrapper.find(MobileDownloads); assert.ok(mobileDownloads.exists()); assert.deepEqual(mobileDownloads.prop("data"), { email: { link_text: "Email yourself a link", }, }); assert.equal(mobileDownloads.prop("handleAction"), handleAction); }); it("should render EmbeddedBackupRestore for 'backup_restore' tile type", () => { const TEST_CONTENT_WITH_EMBEDDED_BACKUP_RESTORE = { tiles: [EMBEDDED_BACKUP_RESTORE_TILE], }; const backupWrapper = mount( ); const embeddedBackupRestore = backupWrapper.find(EmbeddedBackupRestore); assert.ok( embeddedBackupRestore.exists(), "EmbeddedBackupRestore component should be rendered" ); backupWrapper.unmount(); }); it("should handle a single tile object", () => { wrapper.setProps({ content: { tiles: { type: "action_checklist", data: [ { id: "action-checklist-single", targeting: "false", label: { raw: "Single tile label", }, action: { data: { pref: { name: "messaging-system-action.single", value: "false", }, }, type: "SET_PREF", }, }, ], }, }, }); const actionChecklist = wrapper.find(ActionChecklist); assert.ok(actionChecklist.exists()); assert.deepEqual(actionChecklist.prop("content").tiles.data, [ { id: "action-checklist-single", targeting: "false", label: { raw: "Single tile label", }, action: { data: { pref: { name: "messaging-system-action.single", value: "false", }, }, type: "SET_PREF", }, }, ]); }); it("should prefill activeMultiSelect for a MultiSelect tile based on default values", () => { const MULTISELECT_TILE = { type: "multiselect", header: { title: "Multiselect Header", style: { border: "1px solid #ddd", }, }, data: [ { id: "option1", defaultValue: true }, { id: "option2", defaultValue: false }, { id: "option3", defaultValue: true }, ], }; const contentWithMultiselect = { tiles: MULTISELECT_TILE }; wrapper = mount( ); wrapper.update(); sinon.assert.calledOnce(setActiveMultiSelect); sinon.assert.calledWithExactly( setActiveMultiSelect, ["option1", "option3"], "tile-0" ); }); it("should not prefill activeMultiSelect if it is already set", () => { const MULTISELECT_TILE = { type: "multiselect", header: { title: "Multiselect Header", style: { border: "1px solid #ddd", }, }, data: [ { id: "option1", defaultValue: true }, { id: "option2", defaultValue: false }, { id: "option3", defaultValue: true }, ], }; const contentWithMultiselect = { tiles: [MULTISELECT_TILE] }; wrapper = mount( ); wrapper.update(); sinon.assert.notCalled(setActiveMultiSelect); }); it("should render title and subtitle if present", () => { sandbox.stub(window, "AWSendToDeviceEmailsSupported").resolves(true); let TEST_TILE_CONTENT = { tiles: [TITLE_TILE], }; const mountedWrapper = mount( {}} activeMultiSelect={null} setActiveMultiSelect={setActiveMultiSelect} /> ); const tileTitle = mountedWrapper.find(".tile-title"); const tileSubtitle = mountedWrapper.find(".tile-subtitle"); assert.ok(tileTitle.exists(), "Title should render"); assert.ok(tileSubtitle.exists(), "Subtitle should render"); assert.equal( tileTitle.text(), "Tile Title", "Tile title should have correct text" ); assert.equal( tileSubtitle.text(), "Tile Subtitle", "Tile subtitle should have correct text" ); mountedWrapper.unmount(); }); it("should render multiple title and subtitles if multiple tiles contain them", () => { sandbox.stub(window, "AWSendToDeviceEmailsSupported").resolves(true); const SECOND_TITLE_TILE = { type: "mobile_downloads", title: "Tile Title 2", subtitle: "Tile Subtitle 2", data: { email: { link_text: "Email yourself a link", }, }, }; let MULTIPLE_TILES_CONTENT = { tiles: [TITLE_TILE, SECOND_TITLE_TILE], }; const mountedWrapper = mount( {}} activeMultiSelect={null} setActiveMultiSelect={setActiveMultiSelect} setActiveSingleSelectSelection={setActiveSingleSelectSelection} /> ); const tileTitles = mountedWrapper.find(".tile-title"); const tileSubtitles = mountedWrapper.find(".tile-subtitle"); assert.equal(tileTitles.length, 2, "Should render two tile titles"); assert.equal(tileSubtitles.length, 2, "Should render two tile subtitles"); assert.equal( tileTitles.at(0).text(), "Tile Title", "First tile title should have correct text" ); assert.equal( tileSubtitles.at(0).text(), "Tile Subtitle", "First tile subtitle should have correct text" ); assert.equal( tileTitles.at(1).text(), "Tile Title 2", "Second tile title should have correct text" ); assert.equal( tileSubtitles.at(1).text(), "Tile Subtitle 2", "Second tile subtitle should have correct text" ); mountedWrapper.unmount(); }); it("should pre-populate multiple MultiSelect components in the same ContentTiles independently of one another", () => { const FIRST_MULTISELECT_TILE = { type: "multiselect", header: { title: "First Multiselect", }, data: [ { id: "checklist1-option1", defaultValue: true }, { id: "checklist1-option2", defaultValue: false }, ], }; const SECOND_MULTISELECT_TILE = { type: "multiselect", header: { title: "Second Multiselect", }, data: [ { id: "checklist2-option1", defaultValue: false }, { id: "checklist2-option2", defaultValue: true }, ], }; const contentWithMultipleMultiselects = { tiles: [FIRST_MULTISELECT_TILE, SECOND_MULTISELECT_TILE], }; wrapper = mount( ); wrapper.update(); sinon.assert.calledWithExactly( setActiveMultiSelect.getCall(0), ["checklist1-option1"], "tile-0" ); sinon.assert.calledWithExactly( setActiveMultiSelect.getCall(1), ["checklist2-option2"], "tile-1" ); wrapper.unmount(); }); it("should handle interaction between multiselect instances independently of one another", () => { const FIRST_MULTISELECT_TILE = { type: "multiselect", header: { title: "First Multiselect", }, data: [ { id: "checklist1-option1", defaultValue: true, label: "Option 1-1" }, { id: "checklist1-option2", defaultValue: false, label: "Option 1-2" }, ], }; const SECOND_MULTISELECT_TILE = { type: "multiselect", header: { title: "Second Multiselect", }, data: [ { id: "checklist2-option1", defaultValue: false, label: "Option 2-1" }, { id: "checklist2-option2", defaultValue: true, label: "Option 2-2" }, ], }; const contentWithMultipleMultiselects = { tiles: [FIRST_MULTISELECT_TILE, SECOND_MULTISELECT_TILE], }; const initialActiveMultiSelect = { "tile-0": ["checklist1-option1"], "tile-1": ["checklist2-option2"], }; const setScreenMultiSelects = sandbox.stub(); wrapper = mount( ); // First multiselect const firstTileButton = wrapper.find(".tile-header").at(0); assert.ok(firstTileButton.exists(), "First tile header should exist"); firstTileButton.simulate("click"); const firstChecklistInputs = wrapper.find(".multi-select-container input"); assert.equal( firstChecklistInputs.length, 2, "First checklist should have 2 inputs" ); assert.equal( firstChecklistInputs.at(0).prop("checked"), true, "First option should be checked" ); assert.equal( firstChecklistInputs.at(1).prop("checked"), false, "Second option should be unchecked" ); const secondInput = firstChecklistInputs.at(1); secondInput.getDOMNode().checked = true; secondInput.simulate("change"); sinon.assert.calledOnce(setActiveMultiSelect); sinon.assert.calledWithExactly( setActiveMultiSelect, ["checklist1-option1", "checklist1-option2"], "tile-0" ); setActiveMultiSelect.reset(); // Second multiSelect const secondTileButton = wrapper.find(".tile-header").at(1); secondTileButton.simulate("click"); const secondChecklistInputs = wrapper.find(".multi-select-container input"); assert.equal( secondChecklistInputs.length, 2, "Second checklist should have 2 inputs" ); assert.equal( secondChecklistInputs.at(0).prop("checked"), false, "First option should be unchecked" ); assert.equal( secondChecklistInputs.at(1).prop("checked"), true, "Second option should be checked" ); // Uncheck the second checkbox in the second multiselect const lastInput = secondChecklistInputs.at(1); lastInput.getDOMNode().checked = false; lastInput.simulate("change"); sinon.assert.calledOnce(setActiveMultiSelect); sinon.assert.calledWithExactly(setActiveMultiSelect, [], "tile-1"); // Ensure the first multiselect's state wasn't altered by changes to the second multiselect wrapper.setProps({ activeMultiSelect: { "tile-0": ["checklist1-option1", "checklist1-option2"], "tile-1": [], }, }); firstTileButton.simulate("click"); // Get the updated checkboxes from the first checklist const updatedFirstInputs = wrapper.find(".multi-select-container input"); assert.equal( updatedFirstInputs.at(0).prop("checked"), true, "First option should still be checked" ); assert.equal( updatedFirstInputs.at(1).prop("checked"), true, "Second option should still be checked" ); wrapper.unmount(); }); it("should select defaults of single select tiles independently of one another", () => { const SINGLE_SELECT_1 = { type: "single-select", selected: "test1", data: [ { id: "test1", label: { raw: "test1 label", }, }, { defaultValue: true, id: "test2", label: { raw: "test2 label", }, }, ], }; const SINGLE_SELECT_2 = { type: "single-select", selected: "test4", data: [ { id: "test3", label: { raw: "test3 label", }, }, { defaultValue: true, id: "test4", label: { raw: "test4 label", }, }, ], }; const content = { tiles: [SINGLE_SELECT_1, SINGLE_SELECT_2] }; wrapper = mount( ); wrapper.update(); sinon.assert.calledWithExactly( setActiveSingleSelectSelection.getCall(0), "test1", "single-select-0" ); sinon.assert.calledWithExactly( setActiveSingleSelectSelection.getCall(1), "test4", "single-select-1" ); wrapper.unmount(); }); it("should handle interactions with multiple single select tiles independently of one another", () => { const SINGLE_SELECT_1 = { type: "single-select", selected: "test1", data: [ { id: "test1", label: { raw: "test1 label", }, }, { defaultValue: true, id: "test2", label: { raw: "test2 label", }, }, ], }; const SINGLE_SELECT_2 = { type: "single-select", selected: "test4", data: [ { id: "test3", label: { raw: "test3 label", }, }, { defaultValue: true, id: "test4", label: { raw: "test4 label", }, }, ], }; const content = { tiles: [SINGLE_SELECT_1, SINGLE_SELECT_2] }; wrapper = mount( ); wrapper.update(); const tile2 = wrapper.find('input[value="test2"]'); tile2.simulate("click"); sinon.assert.calledWithExactly( setActiveSingleSelectSelection.getCall(2), "test2", "single-select-0" ); const tile3 = wrapper.find('input[value="test3"]'); tile3.simulate("click"); sinon.assert.calledWithExactly( setActiveSingleSelectSelection.getCall(3), "test3", "single-select-1" ); wrapper.unmount(); }); it("should apply styles to label element", () => { const TEST_STYLE = { marginBlock: "5px" }; const tileData = { type: "single-select", selected: "vertical", data: [ { id: "vertical", label: { raw: "Vertical", marginBlock: "5px" }, }, ], }; wrapper = mount( {}} handleAction={() => {}} /> ); const styledDiv = wrapper.find(".text").at(0); assert.deepEqual( styledDiv.prop("style"), TEST_STYLE, "Style prop should match TEST_STYLE" ); wrapper.unmount(); }); it("should apply valid styles from tile.data.style and include minWidth from icon.width", () => { const icon = { width: "101px", }; const style = { paddingBlock: "8px", }; const tileData = { type: "single-select", selected: "test", data: [ { id: "test", icon, label: { raw: "Test" }, style, }, ], }; wrapper = mount( {}} handleAction={() => {}} /> ); const label = wrapper.find("label.select-item").at(0); const labelStyle = label.prop("style"); assert.equal( labelStyle.paddingBlock, "8px", "paddingBlock should be applied" ); assert.equal( labelStyle.minWidth, "101px", "minWidth should be set from icon.width" ); wrapper.unmount(); }); it("restores last tiles focus in Spotlight context and genuine Tab is ignored", async () => { const TAB_GRACE_WINDOW_MS = 250; function nextFrame() { return new Promise(r => requestAnimationFrame(r)); } function delay(ms) { return new Promise(r => setTimeout(r, ms)); } async function waitFor(condition, timeout = TAB_GRACE_WINDOW_MS) { const start = performance.now(); while (!condition()) { await nextFrame(); if (performance.now() - start > timeout) { throw new Error("timeout waiting for condition"); } } } // Pretend we're in a Spotlight dialog so the effect runs const root = document.body.appendChild(document.createElement("div")); root.id = "multi-stage-message-root"; root.className = "onboardingContainer"; root.dataset.page = "spotlight"; const mountNode = document.body.appendChild(document.createElement("div")); const content = { tiles: [{ type: "multiselect", header: { title: "Test" }, data: [] }], }; const focusWrapper = mount(
{}} activeMultiSelect={null} setActiveMultiSelect={() => {}} />
, { attachTo: mountNode } ); // Let the hook attach listeners await nextFrame(); const dialog = focusWrapper.getDOMNode(); const header = dialog.querySelector(".tile-header"); const primary = dialog.querySelector(".action-buttons .primary"); // Record real DOM focus inside tiles header.focus(); header.dispatchEvent(new FocusEvent("focusin", { bubbles: true })); await nextFrame(); // Wait past the tab grace window so this isn’t treated as a real Tab await delay(TAB_GRACE_WINDOW_MS + 1); // Simulate programmatic focus “snap” to an outside control primary.focus(); primary.dispatchEvent(new FocusEvent("focusin", { bubbles: true })); await waitFor(() => document.activeElement === header); assert.strictEqual( document.activeElement, header, "restored focus to tiles header" ); dialog.dispatchEvent( new KeyboardEvent("keydown", { key: "Tab", bubbles: true, cancelable: true, }) ); primary.focus(); primary.dispatchEvent(new FocusEvent("focusin", { bubbles: true })); await nextFrame(); assert.strictEqual( document.activeElement, primary, "did not override genuine Tab focus" ); // Slow tab (>250ms) to action buttons - focus should stay on button header.focus(); header.dispatchEvent(new FocusEvent("focusin", { bubbles: true })); await nextFrame(); // Wait past grace window to simulate slow tabbing await delay(TAB_GRACE_WINDOW_MS + 50); dialog.dispatchEvent( new KeyboardEvent("keydown", { key: "Tab", bubbles: true }) ); primary.focus(); primary.dispatchEvent(new FocusEvent("focusin", { bubbles: true })); assert.strictEqual( document.activeElement, primary, "did not restore focus when slow tabbing to action buttons" ); // Cleanup focusWrapper.unmount(); mountNode.remove(); root.remove(); }); it("passes content.skip_button to EmbeddedBackupRestore as skipButton", () => { const content = { tiles: [EMBEDDED_BACKUP_RESTORE_TILE], skip_button: { label: { raw: "Don't restore" }, action: { navigate: true }, }, }; const mountedWrapper = mount( ); const embeddedBackupComponent = mountedWrapper.find(EmbeddedBackupRestore); assert.ok( embeddedBackupComponent.exists(), "EmbeddedBackupRestore rendered" ); assert.deepEqual( embeddedBackupComponent.prop("skipButton"), content.skip_button, "prop is wired" ); mountedWrapper.unmount(); }); it("renders a single tile without container when there is no header or container style", () => { const SINGLE_TILE_NO_CONTAINER = { tiles: { tile_items: { type: "mobile_downloads", data: { email: { link_text: "Email!" } }, }, container: {}, }, }; const mounted = mount( {}} activeMultiSelect={null} setActiveMultiSelect={setActiveMultiSelect} /> ); assert.isFalse( mounted.find("#content-tiles-container").exists(), "should not render container wrapper" ); assert.equal(mounted.find(".content-tile").length, 1); mounted.unmount(); }); it("passes migration_wizard_options properties to migration-wizard element", () => { const MIGRATION_WIZARD_TILE = { type: "migration-wizard", migration_wizard_options: { force_show_import_all: true, option_expander_title_string: "Custom title", hide_option_expander_subtitle: true, hide_select_all: true, }, }; const content = { tiles: [MIGRATION_WIZARD_TILE], }; const mountedWrapper = mount( ); const embeddedMigrationWizard = mountedWrapper.find( EmbeddedMigrationWizard ); assert.ok( embeddedMigrationWizard.exists(), "EmbeddedMigrationWizard rendered" ); const migrationWizardEl = mountedWrapper.find("migration-wizard"); assert.ok(migrationWizardEl.exists(), "migration-wizard element rendered"); assert.equal( migrationWizardEl.prop("force-show-import-all"), true, "force-show-import-all is set" ); assert.equal( migrationWizardEl.prop("option-expander-title-string"), "Custom title", "option-expander-title-string is set" ); assert.equal( migrationWizardEl.prop("hide-option-expander-subtitle"), true, "hide-option-expander-subtitle is set" ); assert.equal( migrationWizardEl.prop("hide-select-all"), true, "hide-select-all is set" ); mountedWrapper.unmount(); }); it("should render link tiles with external-link-icon and no aria-expanded", () => { const LINK_TILE = { type: "link", header: { title: "link title", }, action: { type: "OPEN_URL", data: { url: "https://example.com" }, }, }; const linkWrapper = mount( ); const tileButton = linkWrapper.find(".tile-header"); assert.strictEqual( tileButton.prop("aria-expanded"), undefined, "Should not have aria-expanded since links don't expand content" ); assert.strictEqual( tileButton.prop("aria-controls"), undefined, "Should not have aria-controls" ); assert.ok( linkWrapper.find(".external-link-icon").exists(), "Should show external-link-icon" ); assert.ok( !linkWrapper.find(".arrow-icon").exists(), "Should not show arrow-icon" ); linkWrapper.unmount(); }); it("should call handleAction when link tile is clicked", () => { const LINK_TILE = { type: "link", id: "test-link", header: { title: "link tile", }, action: { type: "OPEN_URL", data: { url: "https://example.com" }, }, }; let telemetrySpy = sandbox.spy(AboutWelcomeUtils, "sendActionTelemetry"); const linkWrapper = mount( ); const tileButton = linkWrapper.find(".tile-header"); tileButton.simulate("click"); sinon.assert.calledOnce(handleAction); const callArgs = handleAction.firstCall.args; assert.equal( callArgs[1].type, "OPEN_URL", "Should call handleAction with tile action" ); sinon.assert.calledOnce(telemetrySpy); assert.equal( telemetrySpy.firstCall.args[1], "link_test-link_header", "Telemetry should be sent for link tile click" ); assert.ok( !linkWrapper.find(".tile-content").exists(), "Link tiles should not render tile-content" ); linkWrapper.unmount(); }); });