@cnamts/synapse 0.0.0-alpha.0 → 0.0.2-alpha

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 (55) hide show
  1. package/README.md +72 -2
  2. package/dist/design-system-v3.d.ts +234 -2
  3. package/dist/design-system-v3.js +3020 -2142
  4. package/dist/design-system-v3.umd.cjs +2 -2
  5. package/dist/style.css +1 -1
  6. package/package.json +11 -10
  7. package/src/components/CollapsibleList/CollapsibleList.mdx +47 -0
  8. package/src/components/CollapsibleList/CollapsibleList.stories.ts +52 -0
  9. package/src/components/CollapsibleList/CollapsibleList.vue +157 -0
  10. package/src/components/CollapsibleList/tests/CollapsibleList.spec.ts +60 -0
  11. package/src/components/CollapsibleList/types.d.ts +5 -0
  12. package/src/components/Customs/CustomInputSelect/CustomInputSelect.mdx +42 -0
  13. package/src/components/Customs/CustomInputSelect/CustomInputSelect.stories.ts +154 -0
  14. package/src/components/Customs/CustomInputSelect/CustomInputSelect.vue +185 -0
  15. package/src/components/Customs/CustomInputSelect/tests/CustomInputSelect.spec.ts +216 -0
  16. package/src/components/Customs/CustomSelect/CustomSelect.mdx +47 -0
  17. package/src/components/Customs/CustomSelect/CustomSelect.stories.ts +182 -0
  18. package/src/components/Customs/CustomSelect/CustomSelect.vue +188 -0
  19. package/src/components/Customs/CustomSelect/tests/CustomSelect.spec.ts +236 -0
  20. package/src/components/FooterBar/A11yCompliance.ts +9 -0
  21. package/src/components/FooterBar/FooterBar.mdx +115 -0
  22. package/src/components/FooterBar/FooterBar.stories.ts +632 -0
  23. package/src/components/FooterBar/FooterBar.vue +330 -0
  24. package/src/components/FooterBar/config.ts +20 -0
  25. package/src/components/FooterBar/defaultSocialMediaLinks.ts +21 -0
  26. package/src/components/FooterBar/locales.ts +16 -0
  27. package/src/components/FooterBar/tests/FooterBar.spec.ts +167 -0
  28. package/src/components/FooterBar/tests/FooterBarConfig.spec.ts +36 -0
  29. package/src/components/FooterBar/tests/__snapshots__/FooterBar.spec.ts.snap +27 -0
  30. package/src/components/FooterBar/types.d.ts +10 -0
  31. package/src/components/LangBtn/LangBtn.mdx +2 -1
  32. package/src/components/LangBtn/LangBtn.vue +3 -3
  33. package/src/components/Logo/Logo.mdx +26 -0
  34. package/src/components/Logo/Logo.stories.ts +217 -0
  35. package/src/components/Logo/Logo.vue +397 -0
  36. package/src/components/Logo/LogoSize.ts +7 -0
  37. package/src/components/Logo/locales.ts +6 -0
  38. package/src/components/Logo/logoDimensionsMapping.ts +16 -0
  39. package/src/components/Logo/tests/Logo.spec.ts +75 -0
  40. package/src/components/Logo/types.d.ts +8 -0
  41. package/src/components/SocialMediaLinks/DefaultSocialMediaLinks.ts +21 -0
  42. package/src/components/SocialMediaLinks/SocialMediaLinks.mdx +15 -0
  43. package/src/components/SocialMediaLinks/SocialMediaLinks.stories.ts +72 -0
  44. package/src/components/SocialMediaLinks/SocialMediaLinks.vue +92 -0
  45. package/src/components/SocialMediaLinks/locales.ts +3 -0
  46. package/src/components/SocialMediaLinks/tests/DefaultSocialMediaLinks.spec.ts +21 -0
  47. package/src/components/SocialMediaLinks/tests/SocialMediaLinks.spec.ts +89 -0
  48. package/src/components/SocialMediaLinks/tests/__snapshots__/SocialMediaLinks.spec.ts.snap +24 -0
  49. package/src/components/SocialMediaLinks/types.d.ts +5 -0
  50. package/src/components/index.ts +6 -0
  51. package/src/directives/clickOutside.ts +24 -0
  52. package/src/temp/TestDTComponent.vue +6 -10
  53. package/src/temp/gridsTests.vue +0 -4
  54. package/src/utils/propValidator/index.ts +20 -0
  55. package/src/utils/propValidator/tests/propValidator.spec.ts +40 -0
@@ -0,0 +1,75 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import Logo from '../Logo.vue'
3
+ import { describe, it, expect } from 'vitest'
4
+
5
+ import { cnamLightTheme } from '@/designTokens/tokens/cnam/cnamLightTheme'
6
+ import { locales } from '@/components/Logo/locales'
7
+ import { LogoSize } from '@/components/Logo/LogoSize'
8
+ import { logoDimensionsMapping } from '@/components/Logo/logoDimensionsMapping'
9
+
10
+ describe('Logo.vue', () => {
11
+ it('renders correctly with default props', () => {
12
+ const wrapper = mount(Logo)
13
+ expect(wrapper.find('svg').exists()).toBe(true)
14
+ expect(wrapper.find('svg').attributes('fill')).toBe(cnamLightTheme.primary)
15
+ expect(wrapper.find('svg').attributes('aria-label')).toBe('Sécurité sociale, l’Assurance Maladie : Agir ensemble, protéger chacun')
16
+ })
17
+
18
+ it('renders correctly with dark prop', () => {
19
+ const wrapper = mount(Logo, {
20
+ props: { dark: true },
21
+ })
22
+ expect(wrapper.find('svg').attributes('fill')).toBe('#fff')
23
+ })
24
+
25
+ it('renders correctly with avatar prop', () => {
26
+ const wrapper = mount(Logo, {
27
+ props: { avatar: true },
28
+ })
29
+ expect(wrapper.find('svg').attributes('aria-hidden')).toBe('true')
30
+ expect(wrapper.find('svg').attributes('viewBox')).toBe('0 0 64 64')
31
+ })
32
+
33
+ it('renders correctly with hideSignature prop', () => {
34
+ const wrapper = mount(Logo, {
35
+ props: { hideSignature: true },
36
+ })
37
+ expect(wrapper.find('svg').attributes('viewBox')).toBe('0 0 206 64')
38
+ })
39
+
40
+ it('renders correctly with hideOrganism prop', () => {
41
+ const wrapper = mount(Logo, {
42
+ props: { hideOrganism: true },
43
+ })
44
+ expect(wrapper.find('svg').attributes('aria-label')).toBe('l’Assurance Maladie : Agir ensemble, protéger chacun')
45
+ })
46
+
47
+ it('renders correctly with risquePro prop', () => {
48
+ const wrapper = mount(Logo, {
49
+ props: { risquePro: true },
50
+ })
51
+ expect(wrapper.find('svg').attributes('aria-label')).toBe(`${locales.assuranceMaladie}: ${locales.risquePro}`)
52
+ expect(wrapper.find('path').attributes('fill')).toBe(cnamLightTheme.risquePro)
53
+ })
54
+
55
+ it('renders correctly with custom ariaLabel', () => {
56
+ const customLabel = 'Custom Aria Label'
57
+ const wrapper = mount(Logo, {
58
+ props: { ariaLabel: customLabel },
59
+ })
60
+ expect(wrapper.find('svg').attributes('aria-label')).toBe(customLabel)
61
+ })
62
+
63
+ it('renders correctly with size prop', () => {
64
+ const wrapper = mount(Logo, {
65
+ props: { size: LogoSize.SMALL },
66
+ })
67
+ expect(wrapper.find('svg').attributes('width')).toBe(logoDimensionsMapping[LogoSize.SMALL].width)
68
+ expect(wrapper.find('svg').attributes('height')).toBe(logoDimensionsMapping[LogoSize.SMALL].height)
69
+ })
70
+
71
+ it('validates size prop correctly', () => {
72
+ const validator = Logo.props.size.validator
73
+ expect(validator(LogoSize.SMALL)).toBe(true)
74
+ })
75
+ })
@@ -0,0 +1,8 @@
1
+ export interface IndexedObject<T = string> {
2
+ [key: string]: T
3
+ }
4
+
5
+ export interface Dimensions {
6
+ width: string
7
+ height: string
8
+ }
@@ -0,0 +1,21 @@
1
+ import { mdiTwitter, mdiFacebook, mdiLinkedin } from '@mdi/js'
2
+
3
+ import type { SocialMediaLink } from './types'
4
+
5
+ export const defaultSocialMediaLinks: SocialMediaLink[] = [
6
+ {
7
+ icon: mdiLinkedin,
8
+ name: 'LinkedIn',
9
+ href: 'https://www.linkedin.com/company/assurance-maladie/',
10
+ },
11
+ {
12
+ icon: mdiFacebook,
13
+ name: 'Facebook',
14
+ href: 'https://www.facebook.com/AssurMaladie/',
15
+ },
16
+ {
17
+ icon: mdiTwitter,
18
+ name: 'Twitter',
19
+ href: 'https://twitter.com/Assur_Maladie',
20
+ },
21
+ ]
@@ -0,0 +1,15 @@
1
+ import {Controls, Canvas, Meta, Source} from '@storybook/blocks';
2
+
3
+ import * as SocialMediaLinksStories from './SocialMediaLinks.stories.ts';
4
+
5
+ <Meta of={SocialMediaLinksStories} />
6
+
7
+ # SocialMediaLinks
8
+
9
+ Le composant `SocialMediaLinks` est utilisé pour afficher des liens de reseaux sociaux dans le componant `Footer`.
10
+
11
+ <Canvas of={SocialMediaLinksStories.Default} />
12
+
13
+ # API
14
+
15
+ <Controls of={SocialMediaLinksStories.Default} />
@@ -0,0 +1,72 @@
1
+ import SocialMediaLinks from './SocialMediaLinks.vue'
2
+ import type { Meta, StoryObj } from '@storybook/vue3'
3
+ import { mdiFacebook, mdiLinkedin, mdiTwitter } from '@mdi/js'
4
+
5
+ const meta = {
6
+ title: 'Components/SocialMediaLinks',
7
+ component: SocialMediaLinks,
8
+ parameters: {
9
+ layout: 'fullscreen',
10
+ controls: { exclude: ['socialMediaLinks'] },
11
+ },
12
+ args: {
13
+ socialMediaLinks: [
14
+ {
15
+ icon: mdiLinkedin,
16
+ href: 'https://www.linkedin.com/company/assurance-maladie/',
17
+ },
18
+ {
19
+ icon: mdiFacebook,
20
+ href: 'https://twitter.com/Assur_Maladie',
21
+ },
22
+ {
23
+ icon: mdiTwitter,
24
+ href: 'https://twitter.com/Assur_Maladie',
25
+ },
26
+ ],
27
+ },
28
+ argTypes: {
29
+ socialMediaLinks: {
30
+ control: { type: 'object' },
31
+ },
32
+ },
33
+ } as Meta<typeof SocialMediaLinks>
34
+
35
+ export default meta
36
+
37
+ type Story = StoryObj<typeof meta>
38
+
39
+ export const Default: Story = {
40
+ args: {
41
+ links: [
42
+ {
43
+ icon: mdiLinkedin,
44
+ name: 'LinkedIn',
45
+ href: 'https://www.linkedin.com/company/assurance-maladie/',
46
+ },
47
+ {
48
+ icon: mdiFacebook,
49
+ name: 'Facebook',
50
+ href: 'https://twitter.com/Assur_Maladie',
51
+ },
52
+ {
53
+ icon: mdiTwitter,
54
+ name: 'Twitter',
55
+ href: 'https://twitter.com/Assur_Maladie',
56
+ },
57
+ ],
58
+ },
59
+ render: (args) => {
60
+ return {
61
+ components: { SocialMediaLinks },
62
+ setup() {
63
+ return { args }
64
+ },
65
+ template: `
66
+ <div class="d-flex flex-wrap align-center pa-4">
67
+ <SocialMediaLinks :links="args.links" />
68
+ </div>
69
+ `,
70
+ }
71
+ },
72
+ }
@@ -0,0 +1,92 @@
1
+ <script setup lang="ts">
2
+ import type { PropType } from 'vue'
3
+ import { locales } from './locales'
4
+ import type { SocialMediaLink } from './types'
5
+
6
+ const props = defineProps({
7
+ links: {
8
+ type: Array as PropType<SocialMediaLink[]>,
9
+ default: null,
10
+ },
11
+ })
12
+ </script>
13
+
14
+ <template>
15
+ <div class="d-flex flex-column">
16
+ <span class="vd-social-media-links-label text-subtitle-2 text--primary">
17
+ {{ locales.followUs }}
18
+ </span>
19
+
20
+ <ul class="d-flex max-width-none">
21
+ <li
22
+ v-for="(social, index) in props.links"
23
+ :key="index"
24
+ >
25
+ <VBtn
26
+ :id="`social-btn-${index}`"
27
+ :href="social.href"
28
+ target="_blank"
29
+ rel="noopener noreferrer"
30
+ :icon="true"
31
+ :aria-label="`Lien vers ${social.name}`"
32
+ variant="text"
33
+ >
34
+ <VIcon
35
+ size="30px"
36
+ class="vd-social-media-links-icon"
37
+ >
38
+ {{ social.icon }}
39
+ </VIcon>
40
+ </VBtn>
41
+ </li>
42
+ </ul>
43
+ </div>
44
+ </template>
45
+
46
+ <style lang="scss" scoped>
47
+ @use '@/assets/tokens.scss';
48
+
49
+ .vd-social-media-links {
50
+ display: flex;
51
+ flex-direction: column;
52
+ }
53
+
54
+ li {
55
+ list-style: none;
56
+ }
57
+
58
+ .vd-social-media-links-label.text--primary {
59
+ color: tokens.$blue-base;
60
+ font-weight: 600;
61
+ }
62
+
63
+ .v-theme--dark .vd-social-media-links-label.text--primary {
64
+ color: white;
65
+ }
66
+
67
+ .vd-social-media-links-icon {
68
+ color: tokens.$grey-base;
69
+ }
70
+
71
+ .v-btn--icon {
72
+ width: calc(var(--v-btn-height) + 5px);
73
+ height: calc(var(--v-btn-height) + 5px);
74
+ border: 0;
75
+ }
76
+
77
+ .v-theme--dark .v-btn--variant-text:hover :deep() {
78
+ background: rgba(white, 0.1);
79
+ }
80
+
81
+ @media (min-width: 768px) {
82
+ .vd-social-media-links-label {
83
+ text-align: right;
84
+ }
85
+ }
86
+
87
+ @media (max-width: 767px) {
88
+ .vd-social-media-links-label {
89
+ text-align: left;
90
+ }
91
+ }
92
+ </style>
@@ -0,0 +1,3 @@
1
+ export const locales = {
2
+ followUs: 'Suivez-nous :',
3
+ }
@@ -0,0 +1,21 @@
1
+ import { defaultSocialMediaLinks } from '../DefaultSocialMediaLinks'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { mdiLinkedin, mdiTwitter } from '@mdi/js'
4
+
5
+ describe('defaultSocialMediaLinks', () => {
6
+ it('contains the correct number of links', () => {
7
+ expect(defaultSocialMediaLinks.length).toBe(3)
8
+ })
9
+
10
+ it('contains the correct LinkedIn link', () => {
11
+ const linkedinLink = defaultSocialMediaLinks.find(link => link.href === 'https://www.linkedin.com/company/assurance-maladie/')
12
+ expect(linkedinLink).toBeDefined()
13
+ expect(linkedinLink?.icon).toBe(mdiLinkedin)
14
+ })
15
+
16
+ it('contains the correct Twitter link', () => {
17
+ const twitterLink = defaultSocialMediaLinks.find(link => link.href === 'https://twitter.com/Assur_Maladie')
18
+ expect(twitterLink).toBeDefined()
19
+ expect(twitterLink?.icon).toBe(mdiTwitter)
20
+ })
21
+ })
@@ -0,0 +1,89 @@
1
+ import { mount, VueWrapper } from '@vue/test-utils'
2
+ import SocialMediaLinks from '../SocialMediaLinks.vue'
3
+ import { describe, it, expect, afterEach } from 'vitest'
4
+ import { createVuetify } from 'vuetify'
5
+ import * as components from 'vuetify/components'
6
+ import * as directives from 'vuetify/directives'
7
+
8
+ const vuetify = createVuetify({
9
+ components,
10
+ directives,
11
+ })
12
+
13
+ describe('SocialMediaLinks.vue', () => {
14
+ let wrapper: VueWrapper
15
+
16
+ afterEach(() => {
17
+ if (wrapper) {
18
+ wrapper.unmount()
19
+ }
20
+ })
21
+
22
+ it('renders correctly with default props', () => {
23
+ wrapper = mount(SocialMediaLinks, {
24
+ global: {
25
+ plugins: [vuetify],
26
+ },
27
+ props: {
28
+ links: undefined,
29
+ },
30
+ })
31
+ expect(wrapper.html()).toMatchSnapshot()
32
+ })
33
+
34
+ it('renders correctly with provided links', () => {
35
+ const links = [
36
+ { href: 'https://twitter.com', name: 'Twitter', icon: 'mdi-twitter' },
37
+ { href: 'https://facebook.com', name: 'Facebook', icon: 'mdi-facebook' },
38
+ ]
39
+ wrapper = mount(SocialMediaLinks, {
40
+ global: {
41
+ plugins: [vuetify],
42
+ },
43
+ props: {
44
+ links,
45
+ },
46
+ })
47
+ expect(wrapper.html()).toMatchSnapshot()
48
+ })
49
+
50
+ it('renders the correct number of social media links', () => {
51
+ const links = [
52
+ { href: 'https://twitter.com', name: 'Twitter', icon: 'mdi-twitter' },
53
+ { href: 'https://facebook.com', name: 'Facebook', icon: 'mdi-facebook' },
54
+ ]
55
+ wrapper = mount(SocialMediaLinks, {
56
+ global: {
57
+ plugins: [vuetify],
58
+ },
59
+ props: {
60
+ links,
61
+ },
62
+ })
63
+ expect(wrapper.findAll('li').length).toBe(links.length)
64
+ })
65
+
66
+ it('renders no links when links prop is empty array', () => {
67
+ wrapper = mount(SocialMediaLinks, {
68
+ global: {
69
+ plugins: [vuetify],
70
+ },
71
+ props: {
72
+ links: [],
73
+ },
74
+ })
75
+ expect(wrapper.findAll('li').length).toBe(0)
76
+ })
77
+
78
+ it('renders no links when links prop is null', () => {
79
+ wrapper = mount(SocialMediaLinks, {
80
+ global: {
81
+ plugins: [vuetify],
82
+ },
83
+ props: {
84
+ links: undefined,
85
+ },
86
+ })
87
+ expect(wrapper.findAll('li').length).toBe(0)
88
+ })
89
+ })
@@ -0,0 +1,24 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`SocialMediaLinks.vue > renders correctly with default props 1`] = `
4
+ "<div data-v-9d3df26f="" class="d-flex flex-column"><span data-v-9d3df26f="" class="vd-social-media-links-label text-subtitle-2 text--primary">Suivez-nous :</span>
5
+ <ul data-v-9d3df26f="" class="d-flex max-width-none"></ul>
6
+ </div>"
7
+ `;
8
+
9
+ exports[`SocialMediaLinks.vue > renders correctly with provided links 1`] = `
10
+ "<div data-v-9d3df26f="" class="d-flex flex-column"><span data-v-9d3df26f="" class="vd-social-media-links-label text-subtitle-2 text--primary">Suivez-nous :</span>
11
+ <ul data-v-9d3df26f="" class="d-flex max-width-none">
12
+ <li data-v-9d3df26f=""><a data-v-9d3df26f="" class="v-btn v-btn--icon v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-text" href="https://twitter.com" id="social-btn-0" target="_blank" rel="noopener noreferrer" aria-label="Lien vers Twitter"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
13
+ <!----><span class="v-btn__content" data-no-activator=""><i data-v-9d3df26f="" class="mdi-twitter mdi v-icon notranslate v-theme--light vd-social-media-links-icon" style="font-size: 30px; height: 30px; width: 30px;" aria-hidden="true"></i></span>
14
+ <!---->
15
+ <!---->
16
+ </a></li>
17
+ <li data-v-9d3df26f=""><a data-v-9d3df26f="" class="v-btn v-btn--icon v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-text" href="https://facebook.com" id="social-btn-1" target="_blank" rel="noopener noreferrer" aria-label="Lien vers Facebook"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
18
+ <!----><span class="v-btn__content" data-no-activator=""><i data-v-9d3df26f="" class="mdi-facebook mdi v-icon notranslate v-theme--light vd-social-media-links-icon" style="font-size: 30px; height: 30px; width: 30px;" aria-hidden="true"></i></span>
19
+ <!---->
20
+ <!---->
21
+ </a></li>
22
+ </ul>
23
+ </div>"
24
+ `;
@@ -0,0 +1,5 @@
1
+ export interface SocialMediaLink {
2
+ icon: string
3
+ name: string
4
+ href: string
5
+ }
@@ -3,6 +3,12 @@ export { default as Alert } from './Alert/Alert.vue'
3
3
  export { default as CopyBtn } from './CopyBtn/CopyBtn.vue'
4
4
  export { default as SkipLink } from './SkipLink/SkipLink.vue'
5
5
  export { default as BackToTopBtn } from './BackToTopBtn/BackToTopBtn.vue'
6
+ export { default as BackBtn } from './BackBtn/BackBtn.vue'
7
+ export { default as DownloadBtn } from './DownloadBtn/DownloadBtn.vue'
6
8
  export { default as FranceConnectBtn } from './FranceConnectBtn/FranceConnectBtn.vue'
7
9
  export { default as NotificationBar } from './NotificationBar/NotificationBar.vue'
8
10
  export { default as LangBtn } from './LangBtn/LangBtn.vue'
11
+ export { default as Logo } from './Logo/Logo.vue'
12
+ export { default as CollapsibleList } from './CollapsibleList/CollapsibleList.vue'
13
+ export { default as SocialMediaLinks } from './SocialMediaLinks/SocialMediaLinks.vue'
14
+ export { default as FooterBar } from './FooterBar/FooterBar.vue'
@@ -0,0 +1,24 @@
1
+ import { createApp } from 'vue'
2
+
3
+ const app = createApp({})
4
+
5
+ // Click Outside Directive
6
+ app.directive('click-outside', {
7
+ beforeMount(el, binding) {
8
+ el.clickOutsideEvent = function (event: Event) {
9
+ // Check if the click is outside the element
10
+ if (!(el === event.target || el.contains(event.target))) {
11
+ // If so, call the method provided in the binding
12
+ binding.value(event)
13
+ }
14
+ }
15
+ // Attach the event listener
16
+ document.addEventListener('click', el.clickOutsideEvent)
17
+ },
18
+ unmounted(el) {
19
+ // Clean up the event listener
20
+ document.removeEventListener('click', el.clickOutsideEvent)
21
+ },
22
+ })
23
+
24
+ app.mount('#app')
@@ -74,20 +74,16 @@
74
74
  </template>
75
75
 
76
76
  <style lang="scss" scoped>
77
- <<<<<<<< HEAD:src/temp/TestDTComponent.vue
78
- @import '../assets/tokens';
79
- ========
80
- @import '../../../assets/tokens';
81
- >>>>>>>> 82f4bcd (chore(deps): update dependency vue to v3.5.2 (#66)):src/components/Temp/TestDesignTokensComponent/TestDTComponent.vue
77
+ @use '@/assets/tokens.scss';
82
78
 
83
79
  h1 {
84
- color: $orange-base;
80
+ color: tokens.$orange-base;
85
81
  }
86
82
 
87
83
  button {
88
- background-color: $blue-base;
89
- color: $white-base;
90
- border-radius: $radius-rounded;
91
- //padding: $padding-10 $padding-4;
84
+ background-color: tokens.$blue-base;
85
+ color: tokens.$white-base;
86
+ border-radius: tokens.$radius-rounded;
87
+ //padding: tokens.$padding-10 tokens.$padding-4;
92
88
  }
93
89
  </style>
@@ -48,7 +48,3 @@
48
48
  </v-col>
49
49
  </v-container>
50
50
  </template>
51
-
52
- <style lang="scss" scoped>
53
- @import '@/assets/tokens';
54
- </style>
@@ -0,0 +1,20 @@
1
+ /** Validate a prop against a set of values */
2
+ export function propValidator(
3
+ propName: string,
4
+ acceptedValues: string[],
5
+ value: string,
6
+ ): boolean {
7
+ const stringValues = acceptedValues.join('|')
8
+ const formattedValues = `(${stringValues})`
9
+ const valuesRegexp = new RegExp(`^${formattedValues}$`)
10
+
11
+ const isValid = value.match(valuesRegexp) !== null
12
+
13
+ if (!isValid) {
14
+ console.error(
15
+ `Invalid value for the \`${propName}\` prop. Received: "${value}", expected one of: [${acceptedValues.join(', ')}].`,
16
+ )
17
+ }
18
+
19
+ return true
20
+ }
@@ -0,0 +1,40 @@
1
+ import {
2
+ describe,
3
+ it,
4
+ expect,
5
+ afterEach,
6
+ vi,
7
+ beforeEach,
8
+ } from 'vitest'
9
+ import { propValidator } from '../index'
10
+
11
+ const PROP_NAME = 'test'
12
+ const ACCEPTED_VALUES = ['value1', 'value2']
13
+
14
+ describe('propValidator', () => {
15
+ let consoleErrorSpy: unknown
16
+
17
+ beforeEach(() => {
18
+ consoleErrorSpy = vi
19
+ .spyOn(console, 'error')
20
+ .mockImplementation(() => {})
21
+ })
22
+
23
+ it('does not log anything if the prop is valid', () => {
24
+ const result = propValidator(PROP_NAME, ACCEPTED_VALUES, 'value1')
25
+
26
+ expect(result).toBeTruthy()
27
+ expect(consoleErrorSpy).not.toHaveBeenCalled()
28
+ })
29
+
30
+ it('logs an error if the prop is not valid', () => {
31
+ const result = propValidator(PROP_NAME, ACCEPTED_VALUES, 'wrongValue')
32
+
33
+ expect(result).toBeTruthy()
34
+ expect(consoleErrorSpy).toHaveBeenCalled()
35
+ })
36
+
37
+ afterEach(() => {
38
+ vi.restoreAllMocks()
39
+ })
40
+ })