@discourser/design-system 0.26.0 → 0.27.0
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.
- package/dist/figma-codex.json +2 -2
- package/docs/component-catalog.md +469 -0
- package/docs/superpowers/plans/2026-04-03-component-catalog-pipeline.md +667 -0
- package/package.json +3 -2
- package/src/components/__tests__/AbsoluteCenter.test.tsx +31 -0
- package/src/components/__tests__/Divider.test.tsx +38 -0
- package/src/components/__tests__/Group.test.tsx +34 -0
- package/src/components/__tests__/Icon.test.tsx +31 -0
- package/src/components/__tests__/SettingsPopover.test.tsx +39 -0
- package/src/components/__tests__/StudioControls.test.tsx +59 -0
- package/src/components/__tests__/Toaster.test.tsx +24 -0
- package/docs/context-share/ELEVATION_FIX_PLAN.md +0 -903
- package/docs/context-share/fix-checkbox-radio-tokens.md +0 -145
- package/docs/context-share/icon-component-prompt.md +0 -154
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* global describe, it, expect */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { AbsoluteCenter } from '../AbsoluteCenter';
|
|
5
|
+
|
|
6
|
+
describe('AbsoluteCenter', () => {
|
|
7
|
+
it('renders children', () => {
|
|
8
|
+
render(<AbsoluteCenter>Centered content</AbsoluteCenter>);
|
|
9
|
+
expect(screen.getByText('Centered content')).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('renders as a div element', () => {
|
|
13
|
+
const { container } = render(<AbsoluteCenter>Content</AbsoluteCenter>);
|
|
14
|
+
expect(container.querySelector('div')).toBeDefined();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('accepts and passes through className', () => {
|
|
18
|
+
const { container } = render(
|
|
19
|
+
<AbsoluteCenter className="custom-class">Content</AbsoluteCenter>,
|
|
20
|
+
);
|
|
21
|
+
const el = container.querySelector('.custom-class');
|
|
22
|
+
expect(el).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('accepts and forwards arbitrary HTML attributes', () => {
|
|
26
|
+
render(
|
|
27
|
+
<AbsoluteCenter data-testid="absolute-center">Content</AbsoluteCenter>,
|
|
28
|
+
);
|
|
29
|
+
expect(screen.getByTestId('absolute-center')).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* global describe, it, expect */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { Divider } from '../divider';
|
|
5
|
+
|
|
6
|
+
describe('Divider', () => {
|
|
7
|
+
it('renders a div with role="separator" by default', () => {
|
|
8
|
+
render(<Divider />);
|
|
9
|
+
expect(screen.getByRole('separator')).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('has aria-orientation="horizontal" by default', () => {
|
|
13
|
+
render(<Divider />);
|
|
14
|
+
const el = screen.getByRole('separator');
|
|
15
|
+
expect(el.getAttribute('aria-orientation')).toBe('horizontal');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('renders label text when label prop is provided', () => {
|
|
19
|
+
render(<Divider label="OR" />);
|
|
20
|
+
expect(screen.getByText('OR')).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('renders aria-orientation="vertical" when orientation="vertical"', () => {
|
|
24
|
+
render(<Divider orientation="vertical" />);
|
|
25
|
+
const el = screen.getByRole('separator');
|
|
26
|
+
expect(el.getAttribute('aria-orientation')).toBe('vertical');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders without label when no label prop provided', () => {
|
|
30
|
+
const { container } = render(<Divider />);
|
|
31
|
+
expect(container.querySelector('span')).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('accepts className passthrough', () => {
|
|
35
|
+
const { container } = render(<Divider className="my-divider" />);
|
|
36
|
+
expect(container.querySelector('.my-divider')).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* global describe, it, expect */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { Group } from '../Group';
|
|
5
|
+
|
|
6
|
+
describe('Group', () => {
|
|
7
|
+
it('renders children', () => {
|
|
8
|
+
render(<Group>Child content</Group>);
|
|
9
|
+
expect(screen.getByText('Child content')).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('renders as a div', () => {
|
|
13
|
+
const { container } = render(<Group>Content</Group>);
|
|
14
|
+
expect(container.querySelector('div')).toBeDefined();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('accepts data-testid', () => {
|
|
18
|
+
render(<Group data-testid="my-group">Content</Group>);
|
|
19
|
+
expect(screen.getByTestId('my-group')).toBeDefined();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders all children', () => {
|
|
23
|
+
render(
|
|
24
|
+
<Group>
|
|
25
|
+
<span>First</span>
|
|
26
|
+
<span>Second</span>
|
|
27
|
+
<span>Third</span>
|
|
28
|
+
</Group>,
|
|
29
|
+
);
|
|
30
|
+
expect(screen.getByText('First')).toBeDefined();
|
|
31
|
+
expect(screen.getByText('Second')).toBeDefined();
|
|
32
|
+
expect(screen.getByText('Third')).toBeDefined();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* global describe, it, expect */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render } from '@testing-library/react';
|
|
4
|
+
import { Icon } from '../Icon';
|
|
5
|
+
|
|
6
|
+
describe('Icon', () => {
|
|
7
|
+
it('renders as an svg element', () => {
|
|
8
|
+
const { container } = render(<Icon />);
|
|
9
|
+
expect(container.querySelector('svg')).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('renders children (inner SVG content)', () => {
|
|
13
|
+
const { container } = render(
|
|
14
|
+
<Icon>
|
|
15
|
+
<circle cx="12" cy="12" r="10" />
|
|
16
|
+
</Icon>,
|
|
17
|
+
);
|
|
18
|
+
expect(container.querySelector('circle')).toBeDefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('accepts className passthrough', () => {
|
|
22
|
+
const { container } = render(<Icon className="custom-icon" />);
|
|
23
|
+
expect(container.querySelector('.custom-icon')).toBeDefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('accepts aria-label for accessibility', () => {
|
|
27
|
+
const { container } = render(<Icon aria-label="circle icon" />);
|
|
28
|
+
const svg = container.querySelector('svg');
|
|
29
|
+
expect(svg?.getAttribute('aria-label')).toBe('circle icon');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* global describe, it, expect */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { SettingsPopover } from '../SettingsPopover';
|
|
5
|
+
|
|
6
|
+
const defaultProps = {
|
|
7
|
+
userName: 'Jane Doe',
|
|
8
|
+
userTier: 'Pro',
|
|
9
|
+
userEmail: 'jane@example.com',
|
|
10
|
+
actions: [{ key: 'logout', label: 'Logout', onClick: () => {} }],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('SettingsPopover', () => {
|
|
14
|
+
it('renders the trigger button with userName', () => {
|
|
15
|
+
render(<SettingsPopover {...defaultProps} />);
|
|
16
|
+
expect(screen.getByText('Jane Doe')).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('renders userTier in the trigger', () => {
|
|
20
|
+
render(<SettingsPopover {...defaultProps} />);
|
|
21
|
+
expect(screen.getByText('Pro')).toBeDefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('trigger button has correct aria-label (default: "User settings")', () => {
|
|
25
|
+
render(<SettingsPopover {...defaultProps} />);
|
|
26
|
+
expect(screen.getByRole('button', { name: 'User settings' })).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('accepts custom ariaLabel prop', () => {
|
|
30
|
+
render(<SettingsPopover {...defaultProps} ariaLabel="Account menu" />);
|
|
31
|
+
expect(screen.getByRole('button', { name: 'Account menu' })).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders the trigger as a button element', () => {
|
|
35
|
+
render(<SettingsPopover {...defaultProps} />);
|
|
36
|
+
const btn = screen.getByRole('button', { name: 'User settings' });
|
|
37
|
+
expect(btn.tagName.toLowerCase()).toBe('button');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* global describe, it, expect */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { StudioControls } from '../StudioControls';
|
|
5
|
+
|
|
6
|
+
const defaultProps = {
|
|
7
|
+
scenarioName: 'UX Interview Practice',
|
|
8
|
+
scenarioFocus: 'Technical Communication',
|
|
9
|
+
scenarioLevel: 'beginner' as const,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe('StudioControls', () => {
|
|
13
|
+
it('renders the component without crashing', () => {
|
|
14
|
+
const { container } = render(<StudioControls {...defaultProps} />);
|
|
15
|
+
expect(container.firstChild).toBeDefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('renders scenarioName text in the document', () => {
|
|
19
|
+
render(<StudioControls {...defaultProps} />);
|
|
20
|
+
expect(screen.getByText('UX Interview Practice')).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('renders scenarioFocus text in the document', () => {
|
|
24
|
+
render(<StudioControls {...defaultProps} />);
|
|
25
|
+
expect(screen.getByText(/Technical Communication/)).toBeDefined();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders "Scenario Settings" accordion trigger text', () => {
|
|
29
|
+
render(<StudioControls {...defaultProps} />);
|
|
30
|
+
// "Scenario Settings" appears in both the accordion trigger and the settings card heading
|
|
31
|
+
const matches = screen.getAllByText('Scenario Settings');
|
|
32
|
+
expect(matches.length).toBeGreaterThan(0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('renders "Audio Output" accordion trigger text', () => {
|
|
36
|
+
render(<StudioControls {...defaultProps} />);
|
|
37
|
+
expect(screen.getByText('Audio Output')).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('renders "Microphone Input" accordion trigger text', () => {
|
|
41
|
+
render(<StudioControls {...defaultProps} />);
|
|
42
|
+
expect(screen.getByText('Microphone Input')).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders "A/V Recording" accordion trigger text', () => {
|
|
46
|
+
render(<StudioControls {...defaultProps} />);
|
|
47
|
+
expect(screen.getByText('A/V Recording')).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('renders "Display Timer" accordion trigger text', () => {
|
|
51
|
+
render(<StudioControls {...defaultProps} />);
|
|
52
|
+
expect(screen.getByText('Display Timer')).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders "Hide Interviewers" accordion trigger text', () => {
|
|
56
|
+
render(<StudioControls {...defaultProps} />);
|
|
57
|
+
expect(screen.getByText('Hide Interviewers')).toBeDefined();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* global describe, it, expect */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render } from '@testing-library/react';
|
|
4
|
+
import { Toaster, toaster } from '../Toast';
|
|
5
|
+
|
|
6
|
+
describe('Toaster', () => {
|
|
7
|
+
it('toaster imperative API exists and is not null', () => {
|
|
8
|
+
expect(toaster).toBeDefined();
|
|
9
|
+
expect(toaster).not.toBeNull();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('toaster has a create method', () => {
|
|
13
|
+
expect(typeof toaster.create).toBe('function');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('Toaster is a function (renderable component)', () => {
|
|
17
|
+
expect(typeof Toaster).toBe('function');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('renders without crashing', () => {
|
|
21
|
+
const { container } = render(<Toaster />);
|
|
22
|
+
expect(container).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
});
|