Skip to content

Commit

Permalink
Merge pull request learningequality#710 from KshitijThareja/vt-test
Browse files Browse the repository at this point in the history
Implement visual tests for KButton component
  • Loading branch information
AlexVelezLl authored Aug 16, 2024
2 parents 1a28076 + 625cff0 commit 3bddaab
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 11 deletions.
21 changes: 19 additions & 2 deletions docs/pages/testing-playground.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,19 @@
Please do not modify the contents of this file.
-->
<div id="testing-playground" style="padding: 24px">
<component :is="component" v-bind="componentProps" />
<component :is="component" v-bind="componentProps">
<!-- Render slots if provided -->
<template v-for="(slot, name) in slots">
<!-- eslint-disable vue/no-v-html -->
<component
:is="slot.element"
v-if="slot.element"
v-bind="slot.elementProps"
:key="name"
v-html="slot.innerHTML"
/>
</template>
</component>
</div>

</template>
Expand All @@ -32,6 +44,10 @@
* @type {Object} The props to be passed to the dynamically rendered component.
*/
componentProps: {},
/**
* @type {Object} The slots to be passed to the dynamically rendered component.
*/
slots: {},
};
},
Expand All @@ -53,12 +69,13 @@
methods: {
/**
* Handles messages received from the test runner to render a specified component.
* @param {MessageEvent} event - The message event containing the component and its props.
* @param {MessageEvent} event - The message event containing the component and its props.
*/
handleMessage(event) {
if (event.data.type === 'RENDER_COMPONENT') {
this.component = event.data.component;
this.componentProps = event.data.props;
this.slots = event.data.slots || {};
}
},
},
Expand Down
67 changes: 62 additions & 5 deletions jest.conf/visual.testUtils.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,48 @@
import percySnapshot from '@percy/puppeteer';

export async function renderComponent(component, props) {
/**
* Renders a Vue component within the VisualTestingPlayground.
*
* @param {string} component - The name of the Vue component to render.
* @param {Object} props - The props to pass to the component.
* @param {Object} [slots={}] - An object representing the slots to pass to the component.
* The structure of the `slots` object should be as follows:
*
* Example:
* {
* default: {
* element: "div", // or any Vue component like KIcon
* elementProps: { class: "some-class" },
* innerHTML: "<div> Some nested <a>content</a> </div>"
* },
* menu: {
* element: "KDropdownMenu", // named slot content as a Vue component
* elementProps: { options: ['Option 1', 'Option 2'] },
* }
* }
*
* `default` is for the default slot content, which can be raw HTML or another component.
* Other keys correspond to named slots.
*/
export async function renderComponent(component, props, slots = {}) {
const beforeRenderState = await page.evaluate(() => {
const testing_playground = document.querySelector('#testing-playground');
return testing_playground ? testing_playground.innerHTML : '';
});

await page.evaluate(
({ component, props }) => {
({ component, props, slots }) => {
window.postMessage(
{
type: 'RENDER_COMPONENT',
component: component,
props: props,
slots: slots,
},
'*'
);
},
{ component, props }
{ component, props, slots }
);
await page.waitForSelector('#testing-playground');

Expand All @@ -40,8 +65,40 @@ export async function renderComponent(component, props) {
global.expect(isComponentRendered).toBe(true);
}

export async function takeSnapshot(name) {
/**
* Captures a visual snapshot of the current state of the page using Percy.
*
* @param {string} name - The name of the snapshot.
* @param {Object} [options={}] - Additional options to customize the snapshot.
* This can include options such as `widths`, `minHeight`, and some other Percy specific options.
*
* For a full list of available options, refer to the Percy documentation:
* @see https://www.browserstack.com/docs/percy/take-percy-snapshots/snapshots-via-scripts#per-snapshot-configuration
*/
export async function takeSnapshot(name, options = {}) {
if (process.env.TEST_TYPE == 'visual') {
await percySnapshot(page, name);
await percySnapshot(page, name, options);
}
}

export async function delay(time) {
return new Promise(function(resolve) {
setTimeout(resolve, time);
});
}

export const click = async selector => {
await page.locator(selector).click();
};

export const hover = async selector => {
await page.locator(selector).hover();
};

export const scrollToPos = async (selector, scrollOptions) => {
await page.locator(selector).scroll(scrollOptions);
};

export const waitFor = async selector => {
await page.locator(selector).wait();
};
103 changes: 99 additions & 4 deletions lib/buttons-and-links/__tests__/KButton.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import KButton from '../KButton.vue';
import { renderComponent, takeSnapshot } from '../../../jest.conf/visual.testUtils';
import { renderComponent, takeSnapshot, click } from '../../../jest.conf/visual.testUtils';

describe('KButton', () => {
describe('icon related props', () => {
Expand Down Expand Up @@ -67,9 +67,104 @@ describe('KButton', () => {
});

describe.visual('KButton Visual Tests', () => {
it('renders correctly with default props', async () => {
await renderComponent('KButton', { text: 'Test Button' });
await takeSnapshot('KButton - Default');
const snapshotOptions = { widths: [400], minHeight: 512 };

describe('renders correctly with different appearances', () => {
it('renders correctly as primary raised button', async () => {
await renderComponent('KButton', {
text: 'Raised Button',
primary: true,
appearance: 'raised-button',
});
await takeSnapshot('KButton - Primary Raised Button', snapshotOptions);
});
it('renders correctly as secondary raised button', async () => {
await renderComponent('KButton', {
text: 'Raised Button',
primary: false,
appearance: 'raised-button',
});
await takeSnapshot('KButton - Secondary Raised Button', snapshotOptions);
});
it('renders correctly as primary flat button', async () => {
await renderComponent('KButton', {
text: 'Flat Button',
primary: true,
appearance: 'flat-button',
});
await takeSnapshot('KButton - Primary Flat Button', snapshotOptions);
});
it('renders correctly as secondary flat button', async () => {
await renderComponent('KButton', {
text: 'Flat Button',
primary: false,
appearance: 'flat-button',
});
await takeSnapshot('KButton - Secondary Flat Button', snapshotOptions);
});
it('renders correctly as basic link', async () => {
await renderComponent('KButton', { text: 'Basic Link', appearance: 'basic-link' });
await takeSnapshot('KButton - Basic Link', snapshotOptions);
});
});

describe('renders correctly when disabled', () => {
it('renders correctly as disabled raised button', async () => {
await renderComponent('KButton', {
text: 'Raised Button',
disabled: true,
appearance: 'raised-button',
});
await takeSnapshot('KButton - Disabled Raised Button', snapshotOptions);
});
it('renders correctly as disabled flat button', async () => {
await renderComponent('KButton', {
text: 'Flat Button',
disabled: true,
appearance: 'flat-button',
});
await takeSnapshot('KButton - Disabled Flat Button', snapshotOptions);
});
});

describe('renders correctly with icons', () => {
it('renders correctly with icon on the left', async () => {
await renderComponent('KButton', { text: 'Icon Button', icon: 'add' });
await takeSnapshot('KButton - With Icons', snapshotOptions);
});
it('renders correctly with icon on the right', async () => {
await renderComponent('KButton', { text: 'Icon After', iconAfter: 'video' });
await takeSnapshot('KButton - With Icons After', snapshotOptions);
});
});

it('renders correctly with KDropdownMenu slot and shows options on click', async () => {
await renderComponent(
'KButton',
{ text: 'Button with Dropdown' },
{
menu: {
element: 'KDropdownMenu',
elementProps: { options: ['Option 1', 'Option 2'] },
},
}
);
await click('button');
await takeSnapshot('KButton - Dropdown Opened', snapshotOptions);
});

it('should render the default slot when provided', async () => {
await renderComponent(
'KButton',
{ text: 'Button' },
{
default: {
element: 'span',
innerHTML: 'Default Slot',
},
}
);
await takeSnapshot('KButton - With Default Slot', snapshotOptions);
});
});
});

0 comments on commit 3bddaab

Please sign in to comment.