@emulsify/core 3.5.0 → 4.0.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.
Files changed (110) hide show
  1. package/.cli/init.js +40 -31
  2. package/.storybook/_drupal.js +129 -8
  3. package/.storybook/css-components.js +13 -0
  4. package/.storybook/css-dist.js +5 -0
  5. package/.storybook/emulsifyTheme.js +9 -6
  6. package/.storybook/main.js +397 -106
  7. package/.storybook/manager.js +9 -16
  8. package/.storybook/preview.js +88 -110
  9. package/.storybook/utils.js +69 -74
  10. package/README.md +110 -59
  11. package/config/.stylelintrc.json +2 -6
  12. package/config/a11y.config.js +9 -5
  13. package/config/babel.config.js +6 -11
  14. package/config/eslint.config.js +31 -3
  15. package/config/postcss.config.js +5 -0
  16. package/config/vite/entries.js +227 -0
  17. package/config/vite/environment.js +39 -0
  18. package/config/vite/platforms.js +70 -0
  19. package/config/vite/plugins/copy-src-assets.js +76 -0
  20. package/config/vite/plugins/copy-twig-files.js +84 -0
  21. package/config/vite/plugins/css-asset-relativizer.js +40 -0
  22. package/config/vite/plugins/index.js +105 -0
  23. package/config/vite/plugins/mirror-components.js +358 -0
  24. package/config/vite/plugins/require-context.js +311 -0
  25. package/config/vite/plugins/source-file-index.js +184 -0
  26. package/config/vite/plugins/svg-sprite.js +117 -0
  27. package/config/vite/plugins/twig-extension-installers.js +36 -0
  28. package/config/vite/plugins/twig-module.js +1251 -0
  29. package/config/vite/plugins/virtual-twig-asset-sources.js +404 -0
  30. package/config/vite/plugins/virtual-twig-globs.js +136 -0
  31. package/config/vite/plugins/vituum-patch.js +167 -0
  32. package/config/vite/plugins/yaml-module.js +133 -0
  33. package/config/vite/plugins.js +12 -0
  34. package/config/vite/project-config.js +192 -0
  35. package/config/vite/project-extensions.js +177 -0
  36. package/config/vite/project-structure.js +447 -0
  37. package/config/vite/twig-extensions.js +109 -0
  38. package/config/vite/utils/fs-safe.js +66 -0
  39. package/config/vite/utils/paths.js +40 -0
  40. package/config/vite/utils/react-singleton.js +85 -0
  41. package/config/vite/utils/unique.js +36 -0
  42. package/config/vite/vite.config.js +161 -0
  43. package/package.json +164 -75
  44. package/scripts/a11y.js +70 -16
  45. package/scripts/audit-twig-stories.js +378 -0
  46. package/scripts/audit.js +1602 -0
  47. package/scripts/check-node-version.js +18 -0
  48. package/scripts/loadYaml.js +5 -1
  49. package/src/extensions/index.js +8 -0
  50. package/src/extensions/react/index.js +12 -0
  51. package/src/extensions/react/register.js +45 -0
  52. package/src/extensions/shared/attributes.js +308 -0
  53. package/src/extensions/shared/html.js +41 -0
  54. package/src/extensions/shared/lists.js +38 -0
  55. package/src/extensions/shared/object.js +22 -0
  56. package/src/extensions/twig/function-map.js +20 -0
  57. package/src/extensions/twig/functions/add-attributes.js +39 -0
  58. package/src/extensions/twig/functions/bem.js +166 -0
  59. package/src/extensions/twig/index.js +13 -0
  60. package/src/extensions/twig/register.js +95 -0
  61. package/src/extensions/twig/tag-map.js +16 -0
  62. package/src/extensions/twig/tags/switch.js +266 -0
  63. package/src/storybook/index.js +14 -0
  64. package/src/storybook/main-config.js +132 -0
  65. package/src/storybook/platform-behaviors.js +60 -0
  66. package/src/storybook/preview-parameters.js +81 -0
  67. package/src/storybook/render-twig.js +295 -0
  68. package/src/storybook/twig/drupal-filters.js +7 -0
  69. package/src/storybook/twig/include-function.js +109 -0
  70. package/src/storybook/twig/include.js +28 -0
  71. package/src/storybook/twig/reference-paths.js +294 -0
  72. package/src/storybook/twig/resolver.js +318 -0
  73. package/src/storybook/twig/setup.js +39 -0
  74. package/src/storybook/twig/source-events.js +5 -0
  75. package/src/storybook/twig/source-extensions.js +24 -0
  76. package/src/storybook/twig/source-function.js +239 -0
  77. package/src/storybook/twig/source.js +39 -0
  78. package/.all-contributorsrc +0 -45
  79. package/.editorconfig +0 -5
  80. package/.github/ISSUE_TEMPLATE/BUG_REPORT_TEMPLATE.md +0 -18
  81. package/.github/ISSUE_TEMPLATE/FEATURE_REQUEST_TEMPLATE.md +0 -11
  82. package/.github/PULL_REQUEST_TEMPLATE.md +0 -19
  83. package/.github/dependabot.yml +0 -6
  84. package/.github/workflows/addtoprojects.yml +0 -21
  85. package/.github/workflows/contributors.yml +0 -37
  86. package/.github/workflows/lint.yml +0 -22
  87. package/.github/workflows/semantic-release.yml +0 -24
  88. package/.husky/commit-msg +0 -2
  89. package/.husky/pre-commit +0 -2
  90. package/.nvmrc +0 -1
  91. package/.prettierignore +0 -4
  92. package/.storybook/polyfills/twig-include.js +0 -40
  93. package/.storybook/polyfills/twig-resolver.js +0 -70
  94. package/.storybook/polyfills/twig-source.js +0 -65
  95. package/.storybook/webpack.config.js +0 -269
  96. package/CODE_OF_CONDUCT.md +0 -56
  97. package/commitlint.config.js +0 -5
  98. package/config/jest.config.js +0 -19
  99. package/config/webpack/app.js +0 -1
  100. package/config/webpack/loaders.js +0 -167
  101. package/config/webpack/optimizers.js +0 -26
  102. package/config/webpack/plugins.js +0 -283
  103. package/config/webpack/resolves.js +0 -157
  104. package/config/webpack/sdc-loader.js +0 -16
  105. package/config/webpack/webpack.common.js +0 -272
  106. package/config/webpack/webpack.dev.js +0 -41
  107. package/config/webpack/webpack.prod.js +0 -6
  108. package/release.config.cjs +0 -30
  109. package/scripts/a11y.test.js +0 -172
  110. package/scripts/loadYaml.test.js +0 -30
@@ -1,272 +0,0 @@
1
- /**
2
- * @fileoverview Build Webpack entries and export the configuration.
3
- * - Discovers JS/SCSS assets (base + component) via glob patterns
4
- * - Shapes output paths based on platform and SDC (singleDirectoryComponents)
5
- * - Wires up loaders, plugins, and optimizations
6
- */
7
-
8
- import { posix as path } from 'node:path';
9
- import { sync as globSync } from 'glob';
10
- import fs from 'fs-extra';
11
-
12
- import loaders from './loaders.js';
13
- import plugins from './plugins.js';
14
- import resolves from './resolves.js';
15
- import optimizers from './optimizers.js';
16
- import emulsifyConfig from '../../../../../project.emulsify.json' with { type: 'json' };
17
-
18
- /**
19
- * Resolve the directory of this file (without fileURLToPath).
20
- * @type {string}
21
- */
22
- let _filename = decodeURIComponent(new URL(import.meta.url).pathname);
23
- if (process.platform === 'win32' && _filename.startsWith('/')) {
24
- _filename = _filename.slice(1);
25
- }
26
- const _dirname = path.dirname(_filename);
27
-
28
- /** @type {string} Absolute project root (five levels up from this file). */
29
- const projectDir = path.resolve(_dirname, '../../../../..');
30
-
31
- /** @type {boolean} True when a "src/" directory exists (WP layout). */
32
- const hasSrc = fs.pathExistsSync(path.resolve(projectDir, 'src'));
33
-
34
- /** @type {string} The canonical source directory ("src" if present, else "components"). */
35
- const srcDir = hasSrc
36
- ? path.resolve(projectDir, 'src')
37
- : path.resolve(projectDir, 'components');
38
-
39
- /** @type {boolean} True when platform is Drupal (affects component output root). */
40
- const isDrupal = emulsifyConfig?.project?.platform === 'drupal';
41
-
42
- /** @type {boolean} Respect SDC (single-directory-components) layout if explicitly true. */
43
- const SDC = Boolean(emulsifyConfig?.project?.singleDirectoryComponents);
44
-
45
- /** @type {string} Output base for "global" assets. */
46
- const globalOutBase = hasSrc ? 'dist/global' : 'dist';
47
-
48
- /**
49
- * Create a path under the component output root.
50
- * - In Drupal + src layout, components resolve to "components/…"
51
- * - Otherwise, they resolve to "dist/components/…"
52
- * @param {string} subpath - Component-local subpath (no extension).
53
- * @returns {string} Component output path segment.
54
- */
55
- const componentOutPath = (subpath) =>
56
- (isDrupal && hasSrc ? 'components' : 'dist/components') + '/' + subpath;
57
-
58
- /**
59
- * Join segments with POSIX semantics (forward slashes), trimming empties.
60
- * @param {...string} segs - Path segments.
61
- * @returns {string} POSIX-joined path.
62
- */
63
- const pj = (...segs) => path.join(...segs.filter(Boolean));
64
-
65
- /**
66
- * Compute the “dist subpath” for a non-component asset.
67
- * Inserts a type folder ("js" or "css") when SDC = false.
68
- * Drops the original file extension.
69
- * @param {string} absFile - Absolute file path.
70
- * @param {'js'|'css'} type - Asset type.
71
- * @returns {string} Subpath under the global output base (no extension).
72
- */
73
- const distSubpathForBase = (absFile, type) => {
74
- const rel = path.relative(srcDir, absFile);
75
- const dir = path.dirname(rel);
76
- const ext = path.extname(rel);
77
- const name = path.basename(rel, ext);
78
- const outTypeDir = type === 'css' ? 'css' : 'js';
79
- return SDC ? pj(dir, name) : pj(dir, outTypeDir, name);
80
- };
81
-
82
- /**
83
- * Compute the “dist subpath” for a component asset located under "…/components".
84
- * Inserts a type folder ("js" or "css") when SDC = false.
85
- * Drops the original file extension.
86
- * @param {string} absFile - Absolute file path.
87
- * @param {'js'|'scss'} type - Source type (scss maps to 'css').
88
- * @returns {string} Component-local subpath (no extension).
89
- */
90
- const distSubpathForComponent = (absFile, type) => {
91
- const relFromComponents = path.relative(pj(srcDir, 'components'), absFile);
92
- const dir = path.dirname(relFromComponents);
93
- const isStyle = type === 'scss';
94
- const outTypeDir = isStyle ? 'css' : 'js';
95
- const ext = isStyle ? '.scss' : '.js';
96
- const name = path.basename(relFromComponents, ext);
97
- return SDC ? pj(dir, name) : pj(dir, outTypeDir, name);
98
- };
99
-
100
- /**
101
- * Sanitize a file path by removing unwanted characters.
102
- *
103
- * @param {string} inputPath - The file path to sanitize.
104
- * @returns {string} The sanitized file path.
105
- */
106
- const sanitizePath = (inputPath) => inputPath.replace(/[^a-zA-Z0-9/_-]/g, '');
107
-
108
- /**
109
- * Reject keys that could touch object internals even after sanitization.
110
- * @param {string} k
111
- * @returns {boolean}
112
- */
113
- const isDangerousKey = (k) =>
114
- k.includes('__proto__') || k.includes('prototype') || k === 'constructor';
115
-
116
- /**
117
- * Add a file under an entry key; if the key exists, merge to an array.
118
- * Keeps JS before SCSS for deterministic order.
119
- *
120
- * @param {Map<string, string | string[]>} map
121
- * @param {string} key
122
- * @param {string} file
123
- * @returns {void}
124
- */
125
- const addEntry = (map, key, file) => {
126
- const safeKey = sanitizePath(String(key));
127
- if (!safeKey || isDangerousKey(safeKey)) return;
128
-
129
- const current = map.get(safeKey);
130
-
131
- if (!current) {
132
- map.set(safeKey, file);
133
- return;
134
- }
135
-
136
- const arr = Array.isArray(current) ? current : [current];
137
- if (!arr.includes(file)) arr.push(file);
138
-
139
- // Optional: ensure JS comes before SCSS
140
- arr.sort((a, b) => {
141
- const ax = a.endsWith('.js') ? 0 : 1;
142
- const bx = b.endsWith('.js') ? 0 : 1;
143
- return ax - bx || a.localeCompare(b);
144
- });
145
-
146
- map.set(safeKey, arr);
147
- };
148
-
149
- /**
150
- * Safe glob wrapper: returns [] if the pattern is falsy.
151
- * @param {string} pattern - Glob pattern.
152
- * @returns {string[]} Matching file paths.
153
- */
154
- const glob = (pattern) => (pattern ? globSync(pattern) : []);
155
-
156
- /* -------------------------------------------------------------------------- */
157
- /* GLOBS */
158
- /* -------------------------------------------------------------------------- */
159
-
160
- const BaseScssPattern = hasSrc
161
- ? pj(srcDir, '!(components|util)/**/!(_*|cl-*|sb-*).scss')
162
- : '';
163
-
164
- const ComponentScssPattern = hasSrc
165
- ? pj(srcDir, 'components/**/!(_*|cl-*|sb-*).scss')
166
- : pj(srcDir, '**/!(_*|cl-*|sb-*).scss');
167
-
168
- const ComponentLibraryScssPattern = pj(srcDir, '**/*{cl-*,sb-*}.scss');
169
-
170
- const BaseJsPattern = hasSrc
171
- ? pj(srcDir, '!(components|util)/**/!(*.stories|*.component|*.min|*.test).js')
172
- : '';
173
-
174
- const ComponentJsPattern = hasSrc
175
- ? pj(srcDir, 'components/**/!(*.stories|*.component|*.min|*.test).js')
176
- : pj(srcDir, '**/!(*.stories|*.component|*.min|*.test).js');
177
-
178
- /* -------------------------------------------------------------------------- */
179
- /* ENTRY BUILD */
180
- /* -------------------------------------------------------------------------- */
181
-
182
- /**
183
- * Build the complete Webpack entries map.
184
- * @returns {Record<string,string>} Webpack entries.
185
- */
186
- const buildEntries = () => {
187
- /** @type {Map<string, string | string[]>} */
188
- const entries = new Map();
189
-
190
- /* ----------------------------- Base / Global JS ----------------------------- */
191
- for (const file of glob(BaseJsPattern)) {
192
- const sub = distSubpathForBase(file, 'js');
193
- // If no "src/", legacy layout puts global JS directly under "dist/js".
194
- const outRoot = hasSrc ? pj(globalOutBase) : pj('dist', 'js');
195
- addEntry(entries, pj(outRoot, sub), file);
196
- }
197
-
198
- /* --------------------------- Component JS (no dist) -------------------------- */
199
- for (const file of glob(ComponentJsPattern)) {
200
- if (file.includes('/dist/')) continue; // guard against accidental recursion
201
- const sub = distSubpathForComponent(file, 'js');
202
- addEntry(entries, componentOutPath(sub), file);
203
- }
204
-
205
- /* ------------------------------ Base / Global CSS --------------------------- */
206
- for (const file of glob(BaseScssPattern)) {
207
- const sub = distSubpathForBase(file, 'css');
208
- // If no "src/", legacy layout puts global CSS directly under "dist/css".
209
- const outRoot = hasSrc ? pj(globalOutBase) : pj('dist', 'css');
210
- addEntry(entries, pj(outRoot, sub), file);
211
- }
212
-
213
- /* ---------------------------- Component CSS (SCSS) --------------------------- */
214
- for (const file of glob(ComponentScssPattern)) {
215
- const sub = distSubpathForComponent(file, 'scss'); // maps to css
216
- addEntry(entries, componentOutPath(sub), file);
217
- }
218
-
219
- /* -------------------------- Component Library (Storybook) -------------------- */
220
- for (const file of glob(ComponentLibraryScssPattern)) {
221
- const rel = path.relative(srcDir, file).replace(/\.scss$/, '');
222
- addEntry(entries, pj('dist', 'storybook', rel), file);
223
- }
224
-
225
- return Object.fromEntries(entries);
226
- };
227
-
228
- /* -------------------------------------------------------------------------- */
229
- /* WEBPACK CONFIG EXPORT */
230
- /* -------------------------------------------------------------------------- */
231
-
232
- export default {
233
- target: 'web',
234
- stats: { errorDetails: true },
235
- externals: {
236
- drupal: 'Drupal',
237
- drupalSettings: 'drupalSettings',
238
- },
239
- entry: buildEntries(),
240
- module: {
241
- rules: [
242
- loaders.CSSLoader,
243
- loaders.SVGSpriteLoader,
244
- loaders.ImageLoader,
245
- loaders.JSLoader,
246
- loaders.TwigLoader,
247
- ],
248
- },
249
- plugins: [
250
- plugins.RemoveEmptyJS,
251
- plugins.MiniCssExtractPlugin,
252
- plugins.ImageminPlugin,
253
- plugins.SpritePlugin,
254
- plugins.ProgressPlugin,
255
- plugins.CopyTwigPlugin,
256
- plugins.CopyComponentAssetsPlugin,
257
- ...(plugins.CopyGlobalAssetsPlugin ? [plugins.CopyGlobalAssetsPlugin] : []),
258
- plugins.CleanWebpackPlugin,
259
- ],
260
- output: {
261
- path: projectDir,
262
- filename: '[name].js',
263
- },
264
- resolve: resolves.TwigResolve,
265
- optimization: optimizers,
266
- // Quiet deprecation noise from Sass @import warnings
267
- ignoreWarnings: [
268
- (warning) =>
269
- Boolean(warning?.message) &&
270
- /Sass @import rules are deprecated/.test(warning.message),
271
- ],
272
- };
@@ -1,41 +0,0 @@
1
- import fs from 'fs-extra';
2
- import { resolve, dirname } from 'path';
3
- import { merge } from 'webpack-merge';
4
- import common from './webpack.common.js';
5
-
6
- // JSON import syntax may vary; adjust if you need `assert { type: 'json' }` instead
7
- import emulsifyConfig from '../../../../../project.emulsify.json' with { type: 'json' };
8
-
9
- // Create __filename from import.meta.url without fileURLToPath
10
- let _filename = decodeURIComponent(new URL(import.meta.url).pathname);
11
-
12
- // On Windows, remove the leading slash (e.g. "/C:/path" -> "C:/path")
13
- if (process.platform === 'win32' && _filename.startsWith('/')) {
14
- _filename = _filename.slice(1);
15
- }
16
-
17
- const _dirname = dirname(_filename);
18
-
19
- // Get directories for file contexts.
20
- const projectDir = resolve(_dirname, '../../../../..');
21
-
22
- const srcPath = resolve(projectDir, 'src');
23
- const srcExists = fs.pathExistsSync(srcPath);
24
- const isDrupal = emulsifyConfig.project.platform === 'drupal';
25
-
26
- // Always ignore dist
27
- const ignored = ['**/dist/**'];
28
-
29
- // If it’s Drupal and there is no src/, also ignore components
30
- if (isDrupal && !srcExists) {
31
- ignored.push('**/components/**');
32
- }
33
-
34
- export default merge(common, {
35
- mode: 'development',
36
- devtool: 'source-map',
37
- watch: true,
38
- watchOptions: {
39
- ignored,
40
- },
41
- });
@@ -1,6 +0,0 @@
1
- import { merge } from 'webpack-merge';
2
- import common from './webpack.common.js';
3
-
4
- export default merge(common, {
5
- mode: 'production',
6
- });
@@ -1,30 +0,0 @@
1
- // release.config.cjs
2
- module.exports = {
3
- branches: ['main'],
4
- repositoryUrl: 'git@github.com:emulsify-ds/emulsify-core.git',
5
- plugins: [
6
- [
7
- '@semantic-release/commit-analyzer',
8
- {
9
- preset: 'angular',
10
- parserOpts: {
11
- noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING']
12
- }
13
- }
14
- ],
15
- [
16
- '@semantic-release/release-notes-generator',
17
- {
18
- preset: 'angular',
19
- parserOpts: {
20
- noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING']
21
- },
22
- writerOpts: {
23
- commitsSort: ['subject', 'scope']
24
- }
25
- }
26
- ],
27
- ['@semantic-release/npm', { npmPublish: true }],
28
- '@semantic-release/github'
29
- ]
30
- }
@@ -1,172 +0,0 @@
1
- import 'regenerator-runtime/runtime';
2
-
3
- const mockExit = jest
4
- .spyOn(global.process, 'exit')
5
- .mockImplementation(() => {});
6
- jest.mock('pa11y', () => jest.fn());
7
- jest.spyOn(global.console, 'log').mockImplementation(() => {});
8
- const pa11y = require('pa11y');
9
- const path = require('path');
10
- const {
11
- severityToColor,
12
- issueIsValid,
13
- logIssue,
14
- logReport,
15
- lintComponent,
16
- lintReportAndExit,
17
- } = require('./a11y');
18
- const {
19
- ignore,
20
- storybookBuildDir,
21
- pa11y: pa11yConfig,
22
- } = require('../config/a11y.config');
23
-
24
- const STORYBOOK_BUILD_DIR = path.resolve(__dirname, '../', storybookBuildDir);
25
- const STORYBOOK_IFRAME = path.join(STORYBOOK_BUILD_DIR, 'iframe.html');
26
-
27
- pa11y.mockResolvedValue('very official report');
28
-
29
- describe('a11y', () => {
30
- beforeEach(() => {
31
- global.console.log.mockClear();
32
- global.process.exit.mockClear();
33
- });
34
-
35
- it('maps axe issue severity to a label', () => {
36
- // (Name no longer mentions "chalk")
37
- expect.assertions(3);
38
- expect(severityToColor('error')).toBe('red');
39
- expect(severityToColor('warning')).toBe('yellow');
40
- expect(severityToColor('notice')).toBe('blue');
41
- });
42
-
43
- it('identifies invalid issues based on the code or the description', () => {
44
- expect.assertions(3);
45
- expect(
46
- issueIsValid({
47
- code: ignore.codes[0],
48
- runnerExtras: {},
49
- }),
50
- ).toBe(false);
51
- expect(
52
- issueIsValid({
53
- runnerExtras: {
54
- description: ignore.descriptions[0],
55
- },
56
- }),
57
- ).toBe(false);
58
- expect(issueIsValid({ code: 'chicken', runnerExtras: {} })).toBe(true);
59
- });
60
-
61
- it('logs a single issue without color codes', () => {
62
- expect.assertions(1);
63
- logIssue({
64
- type: 'error',
65
- message: 'this chicken is not fried enough.',
66
- context: 'https://example.com',
67
- selector: 'kfc > popeyes > .chicken',
68
- });
69
- expect(global.console.log.mock.calls[0][0]).toMatchInlineSnapshot(`
70
- "
71
- severity: error
72
- message: this chicken is not fried enough.
73
- context: https://example.com
74
- selector: kfc > popeyes > .chicken
75
- "
76
- `);
77
- });
78
-
79
- it('logs a whole report without color codes', () => {
80
- const report = {
81
- issues: [
82
- {
83
- type: 'error',
84
- message: 'this pizza is too soggy',
85
- context: 'https://example.com',
86
- selector: 'pizza > .hut',
87
- runnerExtras: {},
88
- },
89
- {
90
- type: 'error',
91
- message: 'this pasta is undercooked',
92
- context: 'https://example.com',
93
- selector: 'olive > .garden',
94
- runnerExtras: {},
95
- },
96
- ],
97
- pageUrl: 'https://example/component.html',
98
- };
99
- expect(logReport(report)).toBe(true);
100
- expect(global.console.log.mock.calls).toMatchInlineSnapshot(`
101
- Array [
102
- Array [
103
- "Issues found in component: https://example/component.html",
104
- ],
105
- Array [
106
- "
107
- severity: error
108
- message: this pizza is too soggy
109
- context: https://example.com
110
- selector: pizza > .hut
111
- ",
112
- ],
113
- Array [
114
- "
115
- severity: error
116
- message: this pasta is undercooked
117
- context: https://example.com
118
- selector: olive > .garden
119
- ",
120
- ],
121
- ]
122
- `);
123
- });
124
-
125
- it('logs that a component has no issues when a report is empty', () => {
126
- expect(logReport({ issues: [], pageUrl: 'papa-johns' })).toBe(false);
127
- expect(global.console.log.mock.calls[0][0]).toMatchInlineSnapshot(
128
- `"No issues found in component: papa-johns"`,
129
- );
130
- });
131
-
132
- it('calls pa11y with the full path to a component', async () => {
133
- expect.assertions(3);
134
- await expect(lintComponent('chicken-strips')).resolves.toBe(
135
- 'very official report',
136
- );
137
-
138
- // First arg: URL
139
- expect(pa11y.mock.calls[0][0]).toBe(
140
- `${STORYBOOK_IFRAME}?id=chicken-strips`,
141
- );
142
-
143
- // Second arg: options merged with defaults in a11y.js
144
- expect(pa11y.mock.calls[0][1]).toEqual(
145
- expect.objectContaining({
146
- includeNotices: true,
147
- includeWarnings: true,
148
- runners: ['axe'],
149
- ...pa11yConfig,
150
- }),
151
- );
152
- });
153
-
154
- it('runs linter, reports on issues, and exits with code "1" if valid issues are found', async () => {
155
- expect.assertions(1);
156
- pa11y.mockResolvedValueOnce({
157
- issues: [
158
- {
159
- type: 'error',
160
- message: 'these 7 layer supreme burritos do not taste that good',
161
- context: 'https://example.com',
162
- selector: 'taco > bell > .burrito',
163
- runnerExtras: {},
164
- },
165
- ],
166
- pageUrl: '/path/to/taco-bell',
167
- });
168
-
169
- await lintReportAndExit(['taco-bell']);
170
- expect(global.process.exit).toHaveBeenCalledWith(1);
171
- });
172
- });
@@ -1,30 +0,0 @@
1
- import fs from 'fs';
2
- import yaml from 'yaml';
3
- import loadYaml from './loadYaml';
4
-
5
- jest
6
- .spyOn(fs, 'readFileSync')
7
- .mockImplementation(() => 'yaml spaghetti and meatballs');
8
-
9
- jest.spyOn(yaml, 'parse').mockImplementation(() => ({
10
- the: 'yaml spaghetti and meatballs',
11
- }));
12
-
13
- describe('loadYaml', () => {
14
- beforeEach(() => {
15
- fs.readFileSync.mockClear();
16
- yaml.parse.mockClear();
17
- });
18
-
19
- it('can load a yaml file, parse it, and return it', () => {
20
- expect.assertions(3);
21
- expect(loadYaml('./big-phat-burger.yml')).toEqual({
22
- the: 'yaml spaghetti and meatballs',
23
- });
24
- expect(fs.readFileSync).toHaveBeenCalledWith(
25
- `${__dirname}/big-phat-burger.yml`,
26
- 'utf8',
27
- );
28
- expect(yaml.parse).toHaveBeenCalledWith('yaml spaghetti and meatballs');
29
- });
30
- });