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: ""
+ * },
+ * 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);
});
});
});