Skip to content

Commit 74fe240

Browse files
update build
1 parent 90201f4 commit 74fe240

File tree

10 files changed

+213
-105
lines changed

10 files changed

+213
-105
lines changed

packages/ui/.storybook/main.js

Lines changed: 0 additions & 34 deletions
This file was deleted.

packages/ui/.storybook/main.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import path from 'node:path'
2+
import { fileURLToPath } from 'node:url'
3+
import type { StorybookConfig } from '@storybook/vue3-vite'
4+
import { loadConfigFromFile, mergeConfig } from 'vite'
5+
6+
const dirname = path.dirname(fileURLToPath(import.meta.url))
7+
const resolve = (...segments: string[]) => path.resolve(dirname, '..', ...segments)
8+
9+
const config: StorybookConfig = {
10+
stories: ['../stories/**/*.stories.@(js|jsx|ts|tsx)'],
11+
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
12+
async viteFinal(baseConfig) {
13+
const { config: mainConfig } = await loadConfigFromFile(
14+
{ mode: 'development', command: 'serve' },
15+
resolve('vite.config.mts'),
16+
)
17+
18+
if (Array.isArray(mainConfig.plugins)) {
19+
// Storybook already wires its own Vue plugin, so skip ours to avoid duplicates
20+
mainConfig.plugins = mainConfig.plugins.filter((pluginOption) => {
21+
const entries = Array.isArray(pluginOption) ? pluginOption : [pluginOption]
22+
return entries.every((entry) => {
23+
if (!entry || typeof entry !== 'object') {
24+
return true
25+
}
26+
27+
const name = (entry as { name?: string }).name
28+
return !name || !/vite:vue/.test(name)
29+
})
30+
})
31+
}
32+
33+
return mergeConfig(baseConfig, mainConfig)
34+
},
35+
framework: {
36+
name: '@storybook/vue3-vite',
37+
options: {},
38+
},
39+
docs: {
40+
autodocs: true,
41+
},
42+
}
43+
44+
export default config

packages/ui/cypress.config.ts

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { defineConfig } from 'cypress'
2-
import viteConfig from './vite.config'
3-
import { mergeConfig } from 'vite'
2+
import viteConfig from './vite.config.mts'
43
import fs from 'fs'
54

65
function useAxeCoreReader(on: Cypress.PluginEvents) {
76
let content: string | undefined
87

98
on('task', {
10-
readAxeCoreCached() {
9+
async readAxeCoreCached() {
1110
if (!content) {
12-
// use the most correct module resolution
13-
const src = require.resolve('axe-core/axe.min.js')
11+
const { createRequire } = await import('module')
12+
// resolve relative to the current working directory so Node walks up as usual
13+
const requireFromCwd = createRequire(`${process.cwd()}/package.json`)
14+
const src = requireFromCwd.resolve('axe-core/axe.min.js')
1415
content = fs.readFileSync(src, { encoding: 'utf-8' })
1516
}
1617

@@ -19,6 +20,26 @@ function useAxeCoreReader(on: Cypress.PluginEvents) {
1920
})
2021
}
2122

23+
function dedupe<T>(values: T[] | undefined, extras: T[]): T[] {
24+
return Array.from(new Set([...(values ?? []), ...extras]))
25+
}
26+
27+
const componentViteConfig = {
28+
...viteConfig,
29+
resolve: {
30+
...viteConfig.resolve,
31+
alias: {
32+
...(viteConfig.resolve?.alias ?? {}),
33+
vue: 'vue/dist/vue.esm-bundler.js',
34+
},
35+
},
36+
optimizeDeps: {
37+
...viteConfig.optimizeDeps,
38+
include: dedupe(viteConfig.optimizeDeps?.include, ['cypress-plugin-tab']),
39+
exclude: dedupe(viteConfig.optimizeDeps?.exclude, ['platform']),
40+
},
41+
} satisfies typeof viteConfig
42+
2243
export default defineConfig({
2344
component: {
2445
setupNodeEvents(on, config) {
@@ -30,18 +51,8 @@ export default defineConfig({
3051
framework: 'vue',
3152
bundler: 'vite',
3253

33-
// additional opts to the main `vite.config.ts`
34-
viteConfig: mergeConfig(viteConfig, {
35-
resolve: {
36-
alias: {
37-
vue: 'vue/dist/vue.esm-bundler.js',
38-
},
39-
},
40-
optimizeDeps: {
41-
include: ['cypress-plugin-tab'],
42-
exclude: ['platform'],
43-
},
44-
}),
54+
// additional opts to the main `vite.config.mts`
55+
viteConfig: componentViteConfig,
4556
},
4657
},
4758
})

packages/ui/cypress/component/SModal.spec.cy.ts

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ it('Mounts', () => {
3737
<SModal v-model:show="show">
3838
<SModalCard>
3939
<template #title>Title slot</template>
40-
40+
4141
Default slot
4242
</SModalCard>
4343
</SModal>
@@ -105,26 +105,6 @@ describe('Focus trap', () => {
105105
cy.contains('open modal').should('be.focused')
106106
})
107107

108-
// skip: Cypress Component Testing is unstable, and I guess that sandboxing is not working well in every case
109-
// due to this, if you turn on this test, other tests will fail
110-
// FIXME
111-
it.skip('Print warning with a tip in case when no any tabbable nodes inside of modal', () => {
112-
mountFactory({ mountWithoutTabbable: true })
113-
114-
cy.stub(window.console, 'warn').as('consoleWarn')
115-
cy.on('uncaught:exception', (err) => {
116-
if (/focus-trap/.test(err.message)) {
117-
return false
118-
}
119-
})
120-
121-
cy.contains('open modal').click()
122-
123-
cy.get('@consoleWarn')
124-
.should('be.calledWithMatch', /you can disable focus-trap completely by setting `focus-trap` prop to `false`/)
125-
.and('be.calledWithMatch', /\[SModal\]/)
126-
})
127-
128108
it("{esc} doesn't disable focus trap if `close-on-esc=false`", () => {
129109
mountFactory({ closeOnEsc: false })
130110

@@ -619,9 +599,9 @@ describe('Eagering', () => {
619599
},
620600
template: `
621601
Show: {{ show }}
622-
602+
623603
<button @click="show = true">Open</button>
624-
604+
625605
<div id="anchor">
626606
<SModal
627607
v-model:show="show"

packages/ui/cypress/plugins/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const plugin: Cypress.PluginConfig = (on, config) => {
2121
on('dev-server:start', async (options) => {
2222
return startDevServer({
2323
options,
24-
// additional opts to the main `vite.config.ts`
24+
// additional opts to the main `vite.config.mts`
2525
viteConfig: {
2626
resolve: {
2727
alias: {
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest'
2+
import { mount } from '@vue/test-utils'
3+
import { defineComponent, nextTick, ref, shallowRef } from 'vue'
4+
import SModal from './SModal.vue'
5+
import type { FocusTrap } from 'focus-trap'
6+
import { useFocusTrap } from '@/composables/focus-trap'
7+
8+
vi.mock('@/composables/focus-trap', () => ({
9+
useFocusTrap: vi.fn(),
10+
}))
11+
12+
const useFocusTrapMock = vi.mocked(useFocusTrap)
13+
14+
const TestHost = defineComponent({
15+
components: { SModal },
16+
setup() {
17+
const show = ref(false)
18+
19+
function open() {
20+
show.value = true
21+
}
22+
23+
return {
24+
show,
25+
open,
26+
}
27+
},
28+
template: `
29+
<SModal v-model:show="show" :teleport-to="null">
30+
<div />
31+
</SModal>
32+
`,
33+
})
34+
35+
describe('SModal focus trap warnings', () => {
36+
beforeEach(() => {
37+
vi.clearAllMocks()
38+
})
39+
40+
it('logs a warning and deactivates focus trap when activation fails', async () => {
41+
let trap!: FocusTrap
42+
43+
const focusTrapError = new Error('focus trap activation failed')
44+
const activate = vi.fn(() => {
45+
throw focusTrapError
46+
})
47+
const deactivate = vi.fn(() => trap)
48+
const pause = vi.fn(() => trap)
49+
const unpause = vi.fn(() => trap)
50+
const update = vi.fn(() => trap)
51+
52+
trap = {
53+
active: false,
54+
paused: false,
55+
activate: activate as FocusTrap['activate'],
56+
deactivate: deactivate as FocusTrap['deactivate'],
57+
pause: pause as FocusTrap['pause'],
58+
unpause: unpause as FocusTrap['unpause'],
59+
updateContainerElements: update as FocusTrap['updateContainerElements'],
60+
}
61+
62+
useFocusTrapMock.mockReturnValueOnce({
63+
trap: shallowRef(trap),
64+
})
65+
66+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
67+
68+
const wrapper = mount(TestHost)
69+
const host = wrapper.vm as unknown as { open: () => void }
70+
71+
host.open()
72+
await nextTick()
73+
await nextTick()
74+
75+
expect(warnSpy).toHaveBeenCalledTimes(1)
76+
expect(warnSpy.mock.calls[0][0]).toContain('[SModal] focus-trap activation is failed')
77+
expect(warnSpy.mock.calls[0][0]).toContain('Tip: you can disable focus-trap completely by setting `focus-trap` prop to `false`')
78+
expect(warnSpy.mock.calls[0][1]).toBe(focusTrapError)
79+
expect(deactivate).toHaveBeenCalledTimes(1)
80+
81+
warnSpy.mockRestore()
82+
wrapper.unmount()
83+
})
84+
})
Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,44 @@
1-
import { test, expect } from 'vitest'
1+
import { test, expect, vi } from 'vitest'
22
import { mount } from '@vue/test-utils'
33
import SPopover from './SPopover'
44

5+
function expectMountToThrow(template: string, message: string) {
6+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
7+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
8+
try {
9+
expect(() =>
10+
mount({
11+
components: { SPopover },
12+
template,
13+
}),
14+
).toThrowError(message)
15+
} finally {
16+
warnSpy.mockRestore()
17+
errorSpy.mockRestore()
18+
}
19+
}
20+
521
test('Throws an error if no trigger slot', () => {
6-
expect(() =>
7-
mount({
8-
components: { SPopover },
9-
template: `<SPopover />`,
10-
}),
11-
).toThrowError('"trigger" slot is required')
22+
expectMountToThrow(`<SPopover />`, '"trigger" slot is required')
1223
})
1324

1425
test('Throws an error if trigger slot is not a single element', () => {
15-
expect(() =>
16-
mount({
17-
components: { SPopover },
18-
template: `
26+
expectMountToThrow(
27+
`
1928
<SPopover>
2029
<template #trigger>
2130
<span>A</span>
2231
<span>B</span>
2332
</template>
2433
</SPopover>
2534
`,
26-
}),
27-
).toThrowError('"trigger" slot should render exact 1 element')
35+
'"trigger" slot should render exact 1 element',
36+
)
2837
})
2938

3039
test('Throws an error if popper slot renders more than 1 element', () => {
31-
expect(() =>
32-
mount({
33-
components: { SPopover },
34-
template: `
40+
expectMountToThrow(
41+
`
3542
<SPopover>
3643
<template #trigger>
3744
<span>A</span>
@@ -43,6 +50,6 @@ test('Throws an error if popper slot renders more than 1 element', () => {
4350
</template>
4451
</SPopover>
4552
`,
46-
}),
47-
).toThrowError('"popper" slot should return either nothing or the only 1 element')
53+
'"popper" slot should return either nothing or the only 1 element',
54+
)
4855
})

packages/ui/src/util/index.spec.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable vue/one-component-per-file */
2-
import { describe, expect, test } from 'vitest'
2+
import { describe, expect, test, vi } from 'vitest'
33
import { createApp, defineComponent, h, ref } from 'vue'
44
import { bareMetalVModel, forceInject, uniqueElementId, nextIncrementalCounter } from './index'
55

@@ -109,7 +109,14 @@ describe('forceInject', () => {
109109

110110
const container = document.createElement('div')
111111
const app = createApp(Broken)
112-
113-
expect(() => app.mount(container)).toThrowError(/Injection/) // eslint-disable-line @typescript-eslint/no-unsafe-argument
112+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
113+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
114+
115+
try {
116+
expect(() => app.mount(container)).toThrowError(/Injection/) // eslint-disable-line @typescript-eslint/no-unsafe-argument
117+
} finally {
118+
warnSpy.mockRestore()
119+
errorSpy.mockRestore()
120+
}
114121
})
115122
})

0 commit comments

Comments
 (0)