Skip to content

Commit

Permalink
improvements added
Browse files Browse the repository at this point in the history
  • Loading branch information
mmitiche committed Dec 20, 2024
1 parent c1cc906 commit 1f5f86b
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,52 +17,52 @@ const defaultOptions = {
engineId: exampleEngine.id,
label: 'Example Tab',
expression: 'exampleExpression',
isActive: true,
isActive: false,
};

const selectors = {
initializationError: 'c-quantic-component-error',
tabButton: 'button',
};

const mockSearchStatusState = {
const defaultSearchStatusState = {
hasResults: true,
firstSearchExecuted: true,
};
let searchStatusState = defaultSearchStatusState;

const mockBuildTabState = {
const defaultTabState = {
isActive: false,
};

const mockSearchStatus = {
state: mockSearchStatusState,
subscribe: jest.fn((callback) => {
mockSearchStatus.callback = callback;
return jest.fn();
}),
};
let tabState = defaultTabState;

const functionsMocks = {
buildTab: jest.fn(() => ({
state: mockBuildTabState,
subscribe: functionsMocks.subscribe,
state: tabState,
subscribe: functionsMocks.tabStateSubscriber,
select: functionsMocks.select,
})),
buildSearchStatus: jest.fn(() => mockSearchStatus),
subscribe: jest.fn((cb) => {
buildSearchStatus: jest.fn(() => ({
state: searchStatusState,
subscribe: functionsMocks.searchStatusStateSubscriber,
})),
tabStateSubscriber: jest.fn((cb) => {
cb();
return functionsMocks.tabStateUnsubscriber;
}),
searchStatusStateSubscriber: jest.fn((cb) => {
cb();
return functionsMocks.unsubscribe;
return functionsMocks.searchStatusStateUnsubscriber;
}),
unsubscribe: jest.fn(() => {}),
unsubscribeSearchStatus: jest.fn(() => {}),
tabStateUnsubscriber: jest.fn(),
searchStatusStateUnsubscriber: jest.fn(),
exampleTabRendered: jest.fn(),
select: jest.fn(),
};

const expectedActiveTabClass = 'slds-is-active';

function createTestComponent(options = defaultOptions) {
prepareHeadlessState();

const element = createElement('c-quantic-tab', {
is: QuanticTab,
});
Expand All @@ -83,15 +83,6 @@ function prepareHeadlessState() {
};
}

function simulateSearchStatusUpdate(
hasResults = true,
firstSearchExecuted = true
) {
mockSearchStatus.state.hasResults = hasResults;
mockSearchStatus.state.firstSearchExecuted = firstSearchExecuted;
mockSearchStatus.callback();
}

// Helper function to wait until the microtask queue is empty.
function flushPromises() {
return new Promise((resolve) => setTimeout(resolve, 0));
Expand Down Expand Up @@ -135,13 +126,16 @@ function cleanup() {
describe('c-quantic-tab', () => {
beforeEach(() => {
mockSuccessfulHeadlessInitialization();
prepareHeadlessState();
});

afterEach(() => {
tabState = defaultTabState;
searchStatusState = defaultSearchStatusState;
cleanup();
});

describe('controller initialization', () => {
describe('component initialization', () => {
it('should build the tab and search status controllers with the proper parameters', async () => {
createTestComponent();
await flushPromises();
Expand All @@ -166,8 +160,18 @@ describe('c-quantic-tab', () => {
createTestComponent();
await flushPromises();

expect(functionsMocks.subscribe).toHaveBeenCalledTimes(1);
expect(mockSearchStatus.subscribe).toHaveBeenCalledTimes(1);
expect(functionsMocks.tabStateSubscriber).toHaveBeenCalledTimes(1);
expect(functionsMocks.searchStatusStateSubscriber).toHaveBeenCalledTimes(
1
);
});

it('should dispatch the quantic__tabrendered event', async () => {
const element = createTestComponent();
setupEventListeners(element);
await flushPromises();

expect(functionsMocks.exampleTabRendered).toHaveBeenCalledTimes(1);
});
});

Expand All @@ -192,90 +196,77 @@ describe('c-quantic-tab', () => {
});
});

describe('when the component renders', () => {
it('should not show the tab before the initial search completes', async () => {
const element = createTestComponent();
simulateSearchStatusUpdate(true, false);
await flushPromises();
describe('component behavior during the initial search', () => {
describe('when the initial search is not yet executed', () => {
beforeAll(() => {
searchStatusState = {...searchStatusState, firstSearchExecuted: false};
});

const tab = element.shadowRoot.querySelector(selectors.tabButton);
it('should not show the tab before the initial search completes', async () => {
const element = createTestComponent();
await flushPromises();

expect(tab).toBeNull();
});
const tab = element.shadowRoot.querySelector(selectors.tabButton);

it('should show the tab after the initial search completes', async () => {
const element = createTestComponent();
simulateSearchStatusUpdate();
await flushPromises();
expect(tab).toBeNull();
});
});

const tab = element.shadowRoot.querySelector(selectors.tabButton);
describe('when the initial search is executed', () => {
beforeAll(() => {
searchStatusState = {...searchStatusState, firstSearchExecuted: true};
});

expect(tab).not.toBeNull();
expect(tab.textContent).toBe(defaultOptions.label);
expect(tab.title).toEqual(defaultOptions.label);
expect(tab.getAttribute('aria-pressed')).toBe('false');
expect(tab.getAttribute('aria-label')).toBe(defaultOptions.label);
});
it('should show the tab after the initial search completes', async () => {
const element = createTestComponent();
await flushPromises();

it('should dispatch the quantic__tabrendered event', async () => {
const element = createTestComponent();
setupEventListeners(element);
await flushPromises();
const tab = element.shadowRoot.querySelector(selectors.tabButton);

expect(functionsMocks.exampleTabRendered).toHaveBeenCalledTimes(1);
expect(tab).not.toBeNull();
expect(tab.textContent).toBe(defaultOptions.label);
expect(tab.title).toEqual(defaultOptions.label);
expect(tab.getAttribute('aria-pressed')).toBe('false');
expect(tab.getAttribute('aria-label')).toBe(defaultOptions.label);
});
});
});

describe('when the tab is not active', () => {
it('should render the tab without the active class', async () => {
beforeAll(() => {
tabState = {...tabState, isActive: false};
});

it('should not display the tab as an active tab', async () => {
const element = createTestComponent();
await flushPromises();

const tab = element.shadowRoot.querySelector(selectors.tabButton);
tab.click();
await flushPromises();
expect(tab).not.toBeNull();

expect(tab.classList).not.toContain(expectedActiveTabClass);
expect(element.isActive).toBe(false);
});
});

describe('when the tab is active', () => {
it('should render the tab with the active class', async () => {
functionsMocks.buildTab.mockImplementation(() => ({
state: {
isActive: true,
},
subscribe: functionsMocks.subscribe,
select: functionsMocks.select,
}));
beforeAll(() => {
tabState = {...tabState, isActive: true};
});

it('should display the tab as an active tab', async () => {
const element = createTestComponent();
await flushPromises();

const tab = element.shadowRoot.querySelector(selectors.tabButton);
tab.click();
await flushPromises();

expect(tab.classList).toContain(expectedActiveTabClass);
expect(element.isActive).toBe(true);
});
});

describe('when the tab is clicked', () => {
it('should trigger the select method', async () => {
const element = createTestComponent();
await flushPromises();

const tab = element.shadowRoot.querySelector(selectors.tabButton);
expect(tab).not.toBeNull();

await tab.click();
await flushPromises();

expect(functionsMocks.select).toHaveBeenCalled();
});

it('should select the tab and make it active', async () => {
it('should call the select method of the tab controller', async () => {
const element = createTestComponent();
await flushPromises();

Expand All @@ -285,21 +276,19 @@ describe('c-quantic-tab', () => {
await tab.click();
await flushPromises();

expect(element.isActive).toBe(true);
expect(tab.getAttribute('aria-pressed')).toBe('true');
expect(tab.classList).toContain('slds-is-active');
expect(functionsMocks.select).toHaveBeenCalledTimes(1);
});
});

describe('when calling the select method', () => {
it('should select the tab', async () => {
describe('when calling the public select method of the component', () => {
it('should call the select method of the tab controller', async () => {
const element = createTestComponent();
await flushPromises();

await element.select();
await flushPromises();

expect(functionsMocks.select).toHaveBeenCalled();
expect(functionsMocks.select).toHaveBeenCalledTimes(1);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ import {

const pageUrl = 's/quantic-tab';

interface TabBarOptions {}
interface TabOptions {}

type QuanticTabE2EFixtures = {
tab: TabObject;
search: SearchObject;
options: Partial<TabBarOptions>;
options: Partial<TabOptions>;
};

type QuanticTabE2ESearchFixtures = QuanticTabE2EFixtures & {
urlHash: string;
};

type QuanticTabE2EInsightFixtures = QuanticTabE2ESearchFixtures & {
type QuanticTabE2EInsightFixtures = QuanticTabE2EFixtures & {
insightSetup: InsightSetupObject;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Locator, Page, Request} from '@playwright/test';
import type {Locator, Page, Request, Response} from '@playwright/test';
import {isUaSearchEvent} from '../../../../../../playwright/utils/requests';

export class TabObject {
Expand All @@ -10,25 +10,16 @@ export class TabObject {
return this.page.locator('c-quantic-tab');
}

get tabButton(): Locator {
return this.tab.locator('button.slds-tabs_default__item.tab_button');
tabButton(tabLabel): Locator {
return this.tab.getByRole('button', {name: new RegExp(`^${tabLabel}$`)});
}

get activeTabLabel(): Locator {
return this.tab
.locator('button.slds-tabs_default__item.slds-is-active')
.locator('span.slds-tabs_default__link');
get activeTabLabel(): Promise<string | null> {
return this.tab.locator('button.slds-is-active').textContent();
}

tabLabel(tabIndex: number): Promise<String | null> {
return this.tab
.locator('span.slds-tabs_default__link')
.nth(tabIndex)
.textContent();
}

async clickTabButton(tabIndex: number): Promise<void> {
await this.tabButton.nth(tabIndex).click();
async clickTabButton(tabLabel: string): Promise<void> {
await this.tabButton(tabLabel).click();
}

async pressTabThenEnter(): Promise<void> {
Expand All @@ -43,24 +34,46 @@ export class TabObject {
await this.page.keyboard.press('Space');
}

async waitForTabUaAnalytics(actionCause): Promise<Request> {
async waitForTabSearchUaAnalytics(
actionCause,
customChecker?: Function
): Promise<Request> {
const uaRequest = this.page.waitForRequest((request) => {
if (isUaSearchEvent(request)) {
const requestBody = request.postDataJSON?.();
const {customData} = requestBody;

const expectedFields = {
actionCause: actionCause,
originContext: 'Search',
};
return Object.keys(expectedFields).every(
const matchesExpectedFields = Object.keys(expectedFields).every(
(key) => requestBody?.[key] === expectedFields[key]
);

return (
matchesExpectedFields &&
(customChecker ? customChecker(customData) : true)
);
}
return false;
});
return uaRequest;
}

async waitForTabSelectUaAnalytics(): Promise<Request> {
return this.waitForTabUaAnalytics('interfaceChange');
async waitForTabSelectUaAnalytics(expectedFields: object): Promise<Request> {
return this.waitForTabSearchUaAnalytics(
'interfaceChange',
(customData: object) => {
return Object.keys(expectedFields).every(
(key) => customData?.[key] === expectedFields[key]
);
}
);
}

extractActionCauseFromSearchResponse(response: Response) {
const {analytics} = response.request().postDataJSON();
return analytics.actionCause;
}
}
Loading

0 comments on commit 1f5f86b

Please sign in to comment.