diff --git a/docs/pages/testing-playground.vue b/docs/pages/testing-playground.vue index cb06c9f80..a98e349c6 100644 --- a/docs/pages/testing-playground.vue +++ b/docs/pages/testing-playground.vue @@ -7,7 +7,19 @@ Please do not modify the contents of this file. -->
- + + + +
@@ -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: {}, }; }, @@ -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 || {}; } }, }, diff --git a/jest.conf/visual.testUtils.js b/jest.conf/visual.testUtils.js index d7b87db37..812e88264 100644 --- a/jest.conf/visual.testUtils.js +++ b/jest.conf/visual.testUtils.js @@ -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: "
Some nested content
" + * }, + * 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'); @@ -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(); +}; diff --git a/lib/buttons-and-links/__tests__/KButton.spec.js b/lib/buttons-and-links/__tests__/KButton.spec.js index cd359f727..39de3b7a8 100644 --- a/lib/buttons-and-links/__tests__/KButton.spec.js +++ b/lib/buttons-and-links/__tests__/KButton.spec.js @@ -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', () => { @@ -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); }); }); });