@truenas/ui-components 0.5.3 → 0.6.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truenas/ui-components",
3
- "version": "0.5.3",
3
+ "version": "0.6.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public"
@@ -0,0 +1,14 @@
1
+ import type { Config } from 'jest';
2
+
3
+ const config: Config = {
4
+ testEnvironment: 'node',
5
+ transform: {
6
+ '^.+\\.tsx?$': ['ts-jest', {
7
+ tsconfig: '<rootDir>/tsconfig.json',
8
+ }],
9
+ },
10
+ testMatch: ['<rootDir>/**/*.spec.ts'],
11
+ moduleFileExtensions: ['ts', 'js', 'json'],
12
+ };
13
+
14
+ export default config;
@@ -0,0 +1,151 @@
1
+ import {
2
+ escapeTableCell,
3
+ METHOD_TABLE_HEADER,
4
+ methodTableRow,
5
+ PROPERTY_TABLE_HEADER,
6
+ propertyTableRow,
7
+ } from './markdown-table';
8
+
9
+ /**
10
+ * Count the cells a Markdown parser reads out of one table row.
11
+ *
12
+ * The leading and trailing `|` are the row's own fences rather than delimiters, and a
13
+ * backslash-escaped `\|` is content — which is exactly the distinction under test, so it
14
+ * is spelled out here rather than borrowed from the code being tested.
15
+ */
16
+ function cellsIn(row: string): number {
17
+ const trimmed = row.trim();
18
+
19
+ return trimmed.slice(1, -1).split(/(?<!\\)\|/).length;
20
+ }
21
+
22
+ /** The header's own first line decides how many cells every row below it must have. */
23
+ const columnsOf = (header: string): number => cellsIn(header.split('\n')[0]);
24
+
25
+ describe('escapeTableCell', () => {
26
+ it('escapes a pipe so it cannot end the cell', () => {
27
+ expect(escapeTableCell('string | RegExp')).toBe('string \\| RegExp');
28
+ });
29
+
30
+ it('escapes every pipe, not just the first', () => {
31
+ expect(escapeTableCell(`'asc' | 'desc' | '' | null`)).toBe(
32
+ `'asc' \\| 'desc' \\| '' \\| null`
33
+ );
34
+ });
35
+
36
+ it('uses the backslash escape rather than an HTML entity, which a code span would show literally', () => {
37
+ expect(escapeTableCell('A | B')).not.toContain('&#124;');
38
+ });
39
+
40
+ it('collapses newlines, because a row is one line', () => {
41
+ expect(escapeTableCell('first line\n second line')).toBe('first line second line');
42
+ });
43
+
44
+ it('leaves a value with nothing to escape alone', () => {
45
+ expect(escapeTableCell('Promise<void>')).toBe('Promise<void>');
46
+ });
47
+ });
48
+
49
+ describe('methodTableRow', () => {
50
+ const columns = columnsOf(METHOD_TABLE_HEADER);
51
+
52
+ it('has a four-column header', () => {
53
+ expect(columns).toBe(4);
54
+ });
55
+
56
+ it('builds one row per union-typed parameter list, not one per union member', () => {
57
+ const row = methodTableRow({
58
+ name: 'clickActionButton',
59
+ parameters: 'string | RegExp',
60
+ returnType: 'Promise<void>',
61
+ description: 'Clicks an action button in the dialog footer by its label.',
62
+ });
63
+
64
+ expect(cellsIn(row)).toBe(columns);
65
+ });
66
+
67
+ it('keeps the row intact when the union is in the return type', () => {
68
+ const row = methodTableRow({
69
+ name: 'getPlaceholder',
70
+ parameters: '',
71
+ returnType: 'Promise<string | null>',
72
+ description: 'Gets the placeholder text of the input.',
73
+ });
74
+
75
+ expect(cellsIn(row)).toBe(columns);
76
+ });
77
+
78
+ it('keeps the row intact when the parameters and the return type both carry unions', () => {
79
+ const row = methodTableRow({
80
+ name: 'getCardSortDirection',
81
+ parameters: `number, 'enter' | 'space'`,
82
+ returnType: `Promise<'asc' | 'desc' | '' | null>`,
83
+ description: 'Gets the active sort direction in the card layout.',
84
+ });
85
+
86
+ expect(cellsIn(row)).toBe(columns);
87
+ });
88
+
89
+ it('keeps the row intact when the description itself contains a pipe', () => {
90
+ const row = methodTableRow({
91
+ name: 'getType',
92
+ parameters: '',
93
+ returnType: 'Promise<string>',
94
+ description: 'Reads the native type: button | submit | reset.',
95
+ });
96
+
97
+ expect(cellsIn(row)).toBe(columns);
98
+ });
99
+
100
+ it('puts the description in the Description column', () => {
101
+ const row = methodTableRow({
102
+ name: 'clickAction',
103
+ parameters: 'string | RegExp',
104
+ returnType: 'Promise<void>',
105
+ description: "Clicks one of the banner's actions by its label.",
106
+ });
107
+ const cells = row.trim().slice(1, -1).split(/(?<!\\)\|/);
108
+
109
+ expect(cells[3].trim()).toBe("Clicks one of the banner's actions by its label.");
110
+ });
111
+
112
+ it('leaves the Parameters cell empty for a method that takes none', () => {
113
+ const row = methodTableRow({
114
+ name: 'getText',
115
+ parameters: '',
116
+ returnType: 'Promise<string>',
117
+ description: 'Gets the text.',
118
+ });
119
+ const cells = row.trim().slice(1, -1).split(/(?<!\\)\|/);
120
+
121
+ expect(cells[1].trim()).toBe('');
122
+ });
123
+ });
124
+
125
+ describe('propertyTableRow', () => {
126
+ const columns = columnsOf(PROPERTY_TABLE_HEADER);
127
+
128
+ it('has a three-column header', () => {
129
+ expect(columns).toBe(3);
130
+ });
131
+
132
+ it('builds one row per union-typed property', () => {
133
+ const row = propertyTableRow({
134
+ name: 'label',
135
+ type: 'string | RegExp',
136
+ description: "Filters by the action's label text.",
137
+ });
138
+
139
+ expect(cellsIn(row)).toBe(columns);
140
+ });
141
+
142
+ it('keeps the row intact when the description contains a pipe', () => {
143
+ const row = propertyTableRow({
144
+ name: 'orientation',
145
+ type: 'string',
146
+ description: 'One of horizontal | vertical.',
147
+ });
148
+
149
+ expect(cellsIn(row)).toBe(columns);
150
+ });
151
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Markdown table rows for the harness documentation `scripts/generate-harness-docs.ts`
3
+ * generates.
4
+ *
5
+ * These live apart from the generator because that file calls `main()` at the bottom of
6
+ * the module, so importing anything out of it runs the whole build. Everything here is a
7
+ * pure function of its argument, which is what makes the row shape testable at all.
8
+ */
9
+
10
+ /** A harness method, reduced to the fields a table row renders. */
11
+ export interface MethodRowInput {
12
+ name: string;
13
+ parameters: string;
14
+ returnType: string;
15
+ description: string;
16
+ }
17
+
18
+ /** An interface property, reduced to the fields a table row renders. */
19
+ export interface PropertyRowInput {
20
+ name: string;
21
+ type: string;
22
+ description: string;
23
+ }
24
+
25
+ /** Header and delimiter rows for the Methods table. Four columns. */
26
+ export const METHOD_TABLE_HEADER =
27
+ '| Method | Parameters | Returns | Description |\n' +
28
+ '|--------|------------|---------|-------------|\n';
29
+
30
+ /** Header and delimiter rows for an interface's properties table. Three columns. */
31
+ export const PROPERTY_TABLE_HEADER =
32
+ '| Property | Type | Description |\n' +
33
+ '|----------|------|-------------|\n';
34
+
35
+ /**
36
+ * Render one value as the contents of a Markdown table cell.
37
+ *
38
+ * A `|` is a cell delimiter wherever it appears in a table row, and a code span does not
39
+ * protect it: the row is split into cells before its inline content is parsed, so a
40
+ * `string | RegExp` parameter arrives as two cells and every column after it shifts left
41
+ * by one. That is what dropped the Description column of every union-typed harness
42
+ * method.
43
+ *
44
+ * The backslash escape is read during that split, which is why `\|` works inside a code
45
+ * span and `&#124;` does not — an entity inside a code span renders as the six literal
46
+ * characters that spell it.
47
+ *
48
+ * Newlines collapse for the same reason: a row is one line, so a wrapped type or
49
+ * description would otherwise end the table partway through.
50
+ */
51
+ export function escapeTableCell(value: string): string {
52
+ return value
53
+ .replace(/\s*\n+\s*/g, ' ')
54
+ .trim()
55
+ .replace(/\|/g, '\\|');
56
+ }
57
+
58
+ /** One row of the Methods table, newline included. */
59
+ export function methodTableRow(method: MethodRowInput): string {
60
+ const parameters = method.parameters ? `\`${escapeTableCell(method.parameters)}\`` : '';
61
+
62
+ return (
63
+ `| \`${escapeTableCell(method.name)}()\`` +
64
+ ` | ${parameters}` +
65
+ ` | \`${escapeTableCell(method.returnType)}\`` +
66
+ ` | ${escapeTableCell(method.description)} |\n`
67
+ );
68
+ }
69
+
70
+ /** One row of an interface's properties table, newline included. */
71
+ export function propertyTableRow(property: PropertyRowInput): string {
72
+ return (
73
+ `| \`${escapeTableCell(property.name)}\`` +
74
+ ` | \`${escapeTableCell(property.type)}\`` +
75
+ ` | ${escapeTableCell(property.description)} |\n`
76
+ );
77
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "esModuleInterop": true,
7
+ "isolatedModules": true,
8
+ "strict": true,
9
+ "types": ["jest", "node"],
10
+ "rootDir": ".",
11
+ "outDir": "../../dist/out-tsc/scripts"
12
+ },
13
+ "include": ["**/*.ts"]
14
+ }
@@ -0,0 +1,18 @@
1
+ import type { Config } from 'jest';
2
+
3
+ /**
4
+ * `yarn test:scripts` — every build script under this directory that has tests.
5
+ *
6
+ * One Jest project per script package rather than one config rooted here, because
7
+ * `<rootDir>` inside a project config resolves to that project's own directory: each
8
+ * package keeps pointing ts-jest at its own `tsconfig.json`, and adding the next one
9
+ * costs a line here rather than a merge of compiler options.
10
+ */
11
+ const config: Config = {
12
+ projects: [
13
+ '<rootDir>/harness-docs/jest.config.ts',
14
+ '<rootDir>/icon-sprite/jest.config.ts',
15
+ ],
16
+ };
17
+
18
+ export default config;