weg-shared-layout 0.0.22 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ import axe from "axe-core";
2
+ const WCAG_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'];
3
+ const defaultRunOptions = {
4
+ runOnly: { type: 'tag', values: WCAG_TAGS },
5
+ };
6
+ export async function runAxe(element, options) {
7
+ return axe.run(element, {
8
+ ...defaultRunOptions,
9
+ ...options,
10
+ runOnly: options?.runOnly ?? defaultRunOptions.runOnly,
11
+ });
12
+ }
@@ -0,0 +1,10 @@
1
+ import { page } from "vitest/browser";
2
+ export const DESKTOP_VIEWPORT = { width: 1280, height: 720 };
3
+ export const MOBILE_VIEWPORT = { width: 375, height: 667 };
4
+ export async function setViewport(width, height) {
5
+ await page.viewport(width, height);
6
+ }
7
+ export async function waitForUpdate() {
8
+ await new Promise((resolve) => setTimeout(resolve, 0));
9
+ await new Promise((resolve) => requestAnimationFrame(resolve));
10
+ }
@@ -0,0 +1,2 @@
1
+ import axe from 'axe-core';
2
+ export declare function runAxe(element: Element, options?: axe.RunOptions): Promise<axe.AxeResults>;
@@ -0,0 +1,10 @@
1
+ export declare const DESKTOP_VIEWPORT: {
2
+ readonly width: 1280;
3
+ readonly height: 720;
4
+ };
5
+ export declare const MOBILE_VIEWPORT: {
6
+ readonly width: 375;
7
+ readonly height: 667;
8
+ };
9
+ export declare function setViewport(width: number, height: number): Promise<void>;
10
+ export declare function waitForUpdate(): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weg-shared-layout",
3
- "version": "0.0.22",
3
+ "version": "0.0.23",
4
4
  "description": "Shared layout Web Components built with Stencil",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.js",
@@ -62,7 +62,8 @@
62
62
  "@types/node": "^22.13.5",
63
63
  "@vitest/browser-playwright": "^4.0.0",
64
64
  "playwright": "^1.52.0",
65
- "vitest": "^4.0.0"
65
+ "vitest": "^4.0.0",
66
+ "axe-core": "^4.11.4"
66
67
  },
67
68
  "license": "MIT"
68
69
  }
@@ -1,27 +0,0 @@
1
- import { render, h, describe, it, expect } from "@stencil/vitest";
2
- describe('my-component', () => {
3
- it('renders', async () => {
4
- const { root } = await render(h("my-component", null));
5
- await expect(root).toEqualHtml(`
6
- <my-component class="hydrated">
7
- <mock:shadow-root>
8
- <div>
9
- Hello, World! I'm
10
- </div>
11
- </mock:shadow-root>
12
- </my-component>
13
- `);
14
- });
15
- it('renders with values', async () => {
16
- const { root } = await render(h("my-component", { first: "Stencil", middle: "'Don't call me a framework'", last: "JS" }));
17
- await expect(root).toEqualHtml(`
18
- <my-component class="hydrated">
19
- <mock:shadow-root>
20
- <div>
21
- Hello, World! I'm Stencil 'Don't call me a framework' JS
22
- </div>
23
- </mock:shadow-root>
24
- </my-component>
25
- `);
26
- });
27
- });
@@ -1,93 +0,0 @@
1
- import { render, h, describe, it, expect } from "@stencil/vitest";
2
- const SAMPLE_HEADER_LAYOUT = {
3
- header: {
4
- dropdowns: [
5
- {
6
- label: 'Find a job',
7
- items: [{ label: 'Graduates', href: '/search?category=graduates' }],
8
- },
9
- ],
10
- links: [{ label: 'Career advice', href: '/career-advice' }],
11
- signIn: { label: 'Sign in', href: '/account/login' },
12
- signOut: { label: 'Sign out' },
13
- },
14
- };
15
- describe('weg-header', () => {
16
- it('renders safely when layout is undefined', async () => {
17
- const { root } = await render(h("weg-header", null));
18
- expect(root).toBeTruthy();
19
- const header = root.shadowRoot?.querySelector('.header');
20
- expect(header).toBeTruthy();
21
- });
22
- it('parses JSON string layout prop', async () => {
23
- const { root } = await render(h("weg-header", { layout: JSON.stringify(SAMPLE_HEADER_LAYOUT) }));
24
- const text = root.shadowRoot?.textContent ?? '';
25
- expect(text).toContain('Find a job');
26
- expect(text).toContain('Career advice');
27
- expect(text).toContain('Sign in');
28
- });
29
- it('renders dropdown labels and flat links from layout object', async () => {
30
- const { root } = await render(h("weg-header", { layout: SAMPLE_HEADER_LAYOUT }));
31
- const text = root.shadowRoot?.textContent ?? '';
32
- expect(text).toContain('Find a job');
33
- expect(text).toContain('Career advice');
34
- });
35
- it('shows signed-in navigation when signed-in is true', async () => {
36
- const { root } = await render(h("weg-header", { layout: SAMPLE_HEADER_LAYOUT, "signed-in": true, "user-name": "Alex" }));
37
- const text = root.shadowRoot?.textContent ?? '';
38
- expect(text).toContain('Find a job');
39
- expect(text).toContain('Dashboard');
40
- expect(text).toContain('Alex');
41
- expect(text).toContain('Sign out');
42
- expect(text).not.toContain('Sign in');
43
- expect(text).not.toContain('Career advice');
44
- });
45
- it('emits wegAuthClick when auth control is clicked', async () => {
46
- const { root } = await render(h("weg-header", { layout: SAMPLE_HEADER_LAYOUT }));
47
- let detail;
48
- root.addEventListener('wegAuthClick', ((event) => {
49
- detail = event.detail;
50
- event.preventDefault();
51
- }));
52
- const authLink = root.shadowRoot?.querySelector('.desktop .sign-in-link');
53
- expect(authLink).toBeTruthy();
54
- authLink?.click();
55
- expect(detail).toEqual({ action: 'sign-in' });
56
- });
57
- it('emits sign-out action when signed in', async () => {
58
- const { root } = await render(h("weg-header", { layout: SAMPLE_HEADER_LAYOUT, "signed-in": true }));
59
- let detail;
60
- root.addEventListener('wegAuthClick', ((event) => {
61
- detail = event.detail;
62
- event.preventDefault();
63
- }));
64
- const signOutLink = root.shadowRoot?.querySelector('.desktop .sign-out-link');
65
- expect(signOutLink).toBeTruthy();
66
- signOutLink?.click();
67
- expect(detail).toEqual({ action: 'sign-out' });
68
- });
69
- it('opens desktop dropdown on trigger focus with accessible wiring', async () => {
70
- const { root } = await render(h("weg-header", { layout: SAMPLE_HEADER_LAYOUT }));
71
- const trigger = root.shadowRoot?.querySelector('.dropdown-trigger');
72
- expect(trigger).toBeTruthy();
73
- expect(trigger?.getAttribute('aria-haspopup')).toBe('true');
74
- expect(trigger?.getAttribute('aria-expanded')).toBe('false');
75
- expect(trigger?.getAttribute('aria-controls')).toBeTruthy();
76
- trigger?.focus();
77
- await new Promise((r) => setTimeout(r, 0));
78
- expect(trigger?.getAttribute('aria-expanded')).toBe('true');
79
- expect(root.shadowRoot?.querySelector('.dropdown-panel')).toBeTruthy();
80
- });
81
- it('toggles mobile accordion aria-expanded', async () => {
82
- const { root } = await render(h("weg-header", { layout: SAMPLE_HEADER_LAYOUT }));
83
- const openButton = root.shadowRoot?.querySelector('.mobile .icon-button');
84
- expect(openButton).toBeTruthy();
85
- openButton?.click();
86
- await new Promise((r) => setTimeout(r, 0));
87
- const accordionButton = root.shadowRoot?.querySelector('.mobile-nav__row');
88
- expect(accordionButton?.getAttribute('aria-expanded')).toBe('false');
89
- accordionButton?.click();
90
- await new Promise((r) => setTimeout(r, 0));
91
- expect(accordionButton?.getAttribute('aria-expanded')).toBe('true');
92
- });
93
- });
@@ -1,36 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { parseJsonProp, isNonEmptyString, normalizeLinks } from "./layout";
3
- describe('parseJsonProp', () => {
4
- it('returns non-string values unchanged', () => {
5
- expect(parseJsonProp({ header: {} })).toEqual({ header: {} });
6
- });
7
- it('parses JSON strings', () => {
8
- expect(parseJsonProp('{"footer":{}}')).toEqual({ footer: {} });
9
- });
10
- it('returns undefined for invalid or empty strings', () => {
11
- expect(parseJsonProp('')).toBeUndefined();
12
- expect(parseJsonProp('not json')).toBeUndefined();
13
- });
14
- });
15
- describe('isNonEmptyString', () => {
16
- it('accepts trimmed non-empty strings', () => {
17
- expect(isNonEmptyString(' hello ')).toBe(true);
18
- });
19
- it('rejects empty or non-string values', () => {
20
- expect(isNonEmptyString(' ')).toBe(false);
21
- expect(isNonEmptyString(null)).toBe(false);
22
- });
23
- });
24
- describe('normalizeLinks', () => {
25
- it('filters invalid entries and trims valid links', () => {
26
- expect(normalizeLinks([
27
- { label: ' About ', href: ' /about ' },
28
- { label: '', href: '/x' },
29
- { label: 'Missing href' },
30
- null,
31
- ])).toEqual([{ label: 'About', href: '/about' }]);
32
- });
33
- it('returns an empty array for non-arrays', () => {
34
- expect(normalizeLinks(undefined)).toEqual([]);
35
- });
36
- });
@@ -1,16 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { format } from "./utils";
3
- describe('format', () => {
4
- it('returns empty string for no names defined', () => {
5
- expect(format(undefined, undefined, undefined)).toEqual('');
6
- });
7
- it('formats just first names', () => {
8
- expect(format('Joseph', undefined, undefined)).toEqual('Joseph');
9
- });
10
- it('formats first and last names', () => {
11
- expect(format('Joseph', undefined, 'Publique')).toEqual('Joseph Publique');
12
- });
13
- it('formats first, middle and last names', () => {
14
- expect(format('Joseph', 'Quincy', 'Publique')).toEqual('Joseph Quincy Publique');
15
- });
16
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};