Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(quantic): SFINT-5832 Sort E2E tests migrate from Cypress to Playwright #4777

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import QuanticSort from 'c/quanticSort';
import {createElement} from 'lwc';
import * as mockHeadlessLoader from 'c/quanticHeadlessLoader';

const selectors = {
lightningCombobox: 'lightning-combobox',
componentError: 'c-quantic-component-error',
sortDropdown: '[data-cy="sort-dropdown"]',
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
initializationError: 'c-quantic-component-error',
};

const sortVariants = {
default: {
name: 'default',
Expand All @@ -20,25 +27,47 @@ const sortVariants = {
},
};

const sortByLabel = 'Sort By';

jest.mock('c/quanticHeadlessLoader');
jest.mock('@salesforce/label/c.quantic_SortBy', () => ({default: 'Sort By'}), {
virtual: true,
});
jest.mock(
'@salesforce/label/c.quantic_SortBy',
() => ({default: sortByLabel}),
{
virtual: true,
}
);

let buenoMock = {
isString: jest
.fn()
.mockImplementation(
(value) => Object.prototype.toString.call(value) === '[object String]'
),
StringValue: jest.fn(),
RecordValue: jest.fn(),
Schema: jest.fn(() => ({
validate: jest.fn(),
})),
};
lvu285 marked this conversation as resolved.
Show resolved Hide resolved

const successfulBuenoValidationMock = buenoMock;
const unsuccessfulBuenoValidationMock = {
...buenoMock,
Schema: jest.fn(() => ({
validate: () => {
throw new Error();
},
})),
};

function mockBueno() {
jest.spyOn(mockHeadlessLoader, 'getBueno').mockReturnValue(
new Promise(() => {
// @ts-ignore
global.Bueno = {
isString: jest
.fn()
.mockImplementation(
(value) =>
Object.prototype.toString.call(value) === '[object String]'
),
};
})
);
// @ts-ignore
mockHeadlessLoader.getBueno = () => {
// @ts-ignore
global.Bueno = buenoMock;
return new Promise((resolve) => resolve());
};
}

let isInitialized = false;
Expand All @@ -47,43 +76,55 @@ const exampleEngine = {
id: 'exampleEngineId',
};

const mockSearchStatusState = {
const defaultSearchStatusState = {
hasResults: true,
};

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

const functionsMocks = {
buildSort: jest.fn(() => ({
state: {},
subscribe: functionsMocks.subscribe,
subscribe: functionsMocks.sortStateSubscriber,
sortBy: functionsMocks.sortBy,
})),
buildSearchStatus: jest.fn(() => ({
state: searchStatusState,
subscribe: functionsMocks.searchStatusStateSubscriber,
})),
buildCriterionExpression: jest.fn((criterion) => criterion),
buildRelevanceSortCriterion: jest.fn(() => 'relevance'),
buildDateSortCriterion: jest.fn(() => 'date'),
buildSearchStatus: jest.fn(() => mockSearchStatus),
subscribe: jest.fn((cb) => {
sortStateSubscriber: jest.fn((cb) => {
cb();
return functionsMocks.unsubscribe;
return functionsMocks.sortStateUnsubscriber;
}),
unsubscribe: jest.fn(() => {}),
searchStatusStateSubscriber: jest.fn((cb) => {
cb();
return functionsMocks.searchStatusStateUnsubscriber;
}),
sortStateUnsubscriber: jest.fn(),
searchStatusStateUnsubscriber: jest.fn(),
sortBy: jest.fn(),
};

const defaultOptions = {
engineId: exampleEngine.id,
variant: 'default',
};

const expectedSortByLabel = 'Sort By';
/**
* Mocks the return value of the assignedNodes method.
* @param {Array<Element>} assignedElements
*/
function mockSlotAssignedNodes(assignedElements) {
HTMLSlotElement.prototype.assignedNodes = function () {
return assignedElements;
};
}

function createTestComponent(options = defaultOptions) {
function createTestComponent(options = defaultOptions, assignedElements = []) {
prepareHeadlessState();
mockSlotAssignedNodes(assignedElements);

const element = createElement('c-quantic-sort', {
is: QuanticSort,
Expand Down Expand Up @@ -128,6 +169,15 @@ function mockSuccessfulHeadlessInitialization() {
};
}

function mockErroneousHeadlessInitialization() {
// @ts-ignore
mockHeadlessLoader.initializeWithHeadless = (element) => {
if (element instanceof QuanticSort) {
element.setInitializationError();
}
};
}

function cleanup() {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
Expand All @@ -145,14 +195,158 @@ describe('c-quantic-sort', () => {

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

describe('when an initialization error occurs', () => {
beforeEach(() => {
mockErroneousHeadlessInitialization();
});

afterAll(() => {
mockSuccessfulHeadlessInitialization();
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need to do that, since your higher beforeEach will already mockSuccessfulHeadlessInitialization()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true indeed 👍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to remove that afterAll entirely @lvu285


it('should display the initialization error component', async () => {
const element = createTestComponent();
await flushPromises();

const initializationError = element.shadowRoot.querySelector(
selectors.initializationError
);

expect(initializationError).not.toBeNull();
});
});

describe('controller initialization', () => {
it('should subscribe to the headless state changes', async () => {
it('should subscribe to the headless sort and search status state changes', async () => {
createTestComponent();
await flushPromises();

expect(functionsMocks.subscribe).toHaveBeenCalledTimes(1);
expect(functionsMocks.sortStateSubscriber).toHaveBeenCalledTimes(1);
expect(functionsMocks.searchStatusStateSubscriber).toHaveBeenCalledTimes(
1
);
});
});

describe('when no results are found', () => {
beforeAll(() => {
searchStatusState = {...defaultSearchStatusState, hasResults: false};
});

it('should not display the sort dropdown', async () => {
const element = createTestComponent();
await flushPromises();

const sortDropdown = element.shadowRoot.querySelector(
selectors.sortDropdown
);

expect(sortDropdown).toBeNull();
});
});

describe('when a sort option is selected', () => {
it('should call the sortBy method of the sort controller', async () => {
const element = createTestComponent();
await flushPromises();

const lightningCombobox = element.shadowRoot.querySelector(
selectors.lightningCombobox
);
const exampleDefaultSortOptionValue = 'relevance';
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
expect(lightningCombobox).not.toBeNull();

lightningCombobox.dispatchEvent(
new CustomEvent('change', {
detail: {value: exampleDefaultSortOptionValue},
})
);

expect(functionsMocks.sortBy).toHaveBeenCalledTimes(1);
expect(functionsMocks.sortBy).toHaveBeenCalledWith(
exampleDefaultSortOptionValue
);
});
});

describe('when custom sort options are passed', () => {
const exampleSlot = {
value: 'example value',
label: 'example label',
criterion: {
by: 'example field',
order: 'example order',
},
};
const exampleAssignedElements = [exampleSlot];

it('should build the controller with the correct sort option and display the custom sort options', async () => {
const element = createTestComponent(
defaultOptions,
exampleAssignedElements
);
await flushPromises();

expect(functionsMocks.buildSort).toHaveBeenCalledTimes(1);
expect(functionsMocks.buildSort).toHaveBeenCalledWith(exampleEngine, {
initialState: {
criterion: {
by: 'example field',
order: 'example order',
},
},
});
const lightningCombobox = element.shadowRoot.querySelector(
selectors.lightningCombobox
);

expect(lightningCombobox.options).toEqual([exampleSlot]);
});
});

describe('when invalid sort options are passed', () => {
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
beforeEach(() => {
// @ts-ignore
buenoMock = unsuccessfulBuenoValidationMock;
});

afterAll(() => {
buenoMock = successfulBuenoValidationMock;
});
const invalidExampleSlot = {
value: 'example value',
label: '',
criterion: {
by: 'example field',
order: 'example order',
},
};
const exampleAssignedElements = [invalidExampleSlot];

it('should display the component error', async () => {
const element = createTestComponent(
defaultOptions,
exampleAssignedElements
);
await flushPromises();

expect(functionsMocks.buildSort).toHaveBeenCalledTimes(1);
expect(functionsMocks.buildSort).toHaveBeenCalledWith(exampleEngine, {
initialState: {
criterion: {
by: 'example field',
order: 'example order',
},
},
});
const componentError = element.shadowRoot.querySelector(
selectors.componentError
);

expect(componentError).not.toBeNull();
});
});

Expand Down Expand Up @@ -187,7 +381,7 @@ describe('c-quantic-sort', () => {

const sortLabel = element.shadowRoot.querySelector(variant.labelSelector);

expect(sortLabel.value).toBe(expectedSortByLabel);
expect(sortLabel.value).toBe(sortByLabel);
});
});

Expand Down Expand Up @@ -228,7 +422,7 @@ describe('c-quantic-sort', () => {

const sortLabel = element.shadowRoot.querySelector(variant.labelSelector);

expect(sortLabel.textContent).toBe(expectedSortByLabel);
expect(sortLabel.textContent).toBe(sortByLabel);
expect(sortLabel.classList).toContain('slds-text-heading_small');
});
});
Expand Down
Loading
Loading