ui-thing 0.0.4 → 0.0.6

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,10 +1,13 @@
1
1
  {
2
2
  "name": "ui-thing",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "CLI used to add Nuxt 3 components to a project",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "main": "./dist/index.js",
8
+ "repository": {
9
+ "url": "https://github.com/BayBreezy/ui-thing-cli"
10
+ },
8
11
  "publishConfig": {
9
12
  "access": "public"
10
13
  },
@@ -16,7 +19,9 @@
16
19
  "dev": "tsup --watch",
17
20
  "start": "node dist/index.js",
18
21
  "format": "npx prettier --write .",
19
- "release": "npm run build && npx changelogen@latest --release && npm publish && git push --follow-tags"
22
+ "release": "npm run build && npx changelogen@latest --release && npm publish && git push --follow-tags",
23
+ "test": "vitest",
24
+ "coverage": "vitest run --coverage"
20
25
  },
21
26
  "keywords": [
22
27
  "cli",
@@ -36,6 +41,7 @@
36
41
  "license": "MIT",
37
42
  "dependencies": {
38
43
  "boxen": "^7.1.1",
44
+ "build": "^0.1.4",
39
45
  "c12": "^1.5.1",
40
46
  "commander": "^11.1.0",
41
47
  "defu": "^6.1.3",
@@ -49,14 +55,17 @@
49
55
  "prompts": "^2.4.2"
50
56
  },
51
57
  "devDependencies": {
58
+ "@gmrchk/cli-testing-library": "^0.1.2",
52
59
  "@ianvs/prettier-plugin-sort-imports": "^4.1.1",
53
- "@types/figlet": "^1.5.7",
54
- "@types/fs-extra": "^11.0.3",
55
- "@types/lodash": "^4.14.200",
56
- "@types/node": "^20.8.10",
57
- "@types/prompts": "^2.4.7",
60
+ "@types/figlet": "^1.5.8",
61
+ "@types/fs-extra": "^11.0.4",
62
+ "@types/lodash": "^4.14.201",
63
+ "@types/node": "^20.9.0",
64
+ "@types/prompts": "^2.4.8",
65
+ "@vitest/coverage-v8": "^0.34.6",
58
66
  "magicast": "^0.3.0",
59
67
  "tsup": "^7.2.0",
60
- "typescript": "^5.2.2"
68
+ "typescript": "^5.2.2",
69
+ "vitest": "^0.34.6"
61
70
  }
62
71
  }
package/src/comp.ts CHANGED
@@ -435,7 +435,7 @@ export default [
435
435
  fileName: "Checkbox/Checkbox.vue",
436
436
  dirPath: "components/UI",
437
437
  fileContent:
438
- '<template>\r\n <CheckboxRoot v-bind="forwarded" :class="styles({ class: props.class })">\r\n <slot>\r\n <Transition enter-active-class="transition" enter-from-class="opacity-0 scale-0">\r\n <UICheckboxIndicator :icon="icon" />\r\n </Transition>\r\n </slot>\r\n </CheckboxRoot>\r\n</template>\r\n\r\n<script lang="ts" setup>\r\n import { CheckboxRoot, useForwardPropsEmits } from "radix-vue";\r\n import type { CheckboxRootEmits, CheckboxRootProps } from "radix-vue";\r\n\r\n const props = defineProps<\r\n CheckboxRootProps & {\r\n class?: any;\r\n id?: string;\r\n icon?: string;\r\n }\r\n >();\r\n\r\n const emit = defineEmits<CheckboxRootEmits>();\r\n const forwarded = useForwardPropsEmits(props, emit);\r\n\r\n const styles = tv({\r\n base: "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",\r\n });\r\n</script>\r\n',
438
+ '<template>\r\n <CheckboxRoot v-bind="forwarded" :class="styles({ class: props.class })">\r\n <slot>\r\n <Transition enter-active-class="transition" enter-from-class="opacity-0 scale-0">\r\n <UICheckboxIndicator :icon="icon" />\r\n </Transition>\r\n </slot>\r\n </CheckboxRoot>\r\n</template>\r\n\r\n<script lang="ts" setup>\r\n import { CheckboxRoot, useForwardPropsEmits } from "radix-vue";\r\n import type { CheckboxRootEmits, CheckboxRootProps } from "radix-vue";\r\n\r\n const props = defineProps<\r\n CheckboxRootProps & {\r\n class?: any;\r\n id?: string;\r\n icon?: string;\r\n }\r\n >();\r\n\r\n const emit = defineEmits<CheckboxRootEmits>();\r\n const forwarded = useForwardPropsEmits(props, emit);\r\n\r\n const styles = tv({\r\n base: "peer h-[18px] w-[18px] shrink-0 rounded-sm border border-primary ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground md:h-4 md:w-4",\r\n });\r\n</script>\r\n',
439
439
  },
440
440
  {
441
441
  fileName: "Checkbox/Indicator.vue",
@@ -988,7 +988,7 @@ export default [
988
988
  fileName: "Input.vue",
989
989
  dirPath: "components/UI",
990
990
  fileContent:
991
- '<template>\r\n <input :class="styles({ class: props.class })" v-bind="forwarded" v-model="localModel" />\r\n</template>\r\n\r\n<script lang="ts" setup>\r\n import { useForwardPropsEmits } from "radix-vue";\r\n\r\n const props = withDefaults(\r\n defineProps<{\r\n class?: any;\r\n id?: string;\r\n name?: string;\r\n placeholder?: string;\r\n disabled?: boolean;\r\n required?: boolean;\r\n type?: string;\r\n modelValue?: any;\r\n }>(),\r\n { type: "text" }\r\n );\r\n const emits = defineEmits<{\r\n "update:modelValue": [value: any];\r\n }>();\r\n const forwarded = useForwardPropsEmits(useOmit(props, ["class"]), emits);\r\n\r\n const localModel = useVModel(props, "modelValue", emits);\r\n\r\n const styles = tv({\r\n base: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground file:hover:cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm",\r\n });\r\n</script>\r\n',
991
+ '<template>\r\n <input :class="styles({ class: props.class })" v-bind="forwarded" v-model="localModel" />\r\n</template>\r\n\r\n<script lang="ts" setup>\r\n import { useForwardPropsEmits } from "radix-vue";\r\n\r\n const props = withDefaults(\r\n defineProps<{\r\n class?: any;\r\n id?: string;\r\n name?: string;\r\n placeholder?: string;\r\n disabled?: boolean;\r\n required?: boolean;\r\n type?: string;\r\n modelValue?: any;\r\n }>(),\r\n { type: "text" }\r\n );\r\n const emits = defineEmits<{\r\n "update:modelValue": [value: any];\r\n }>();\r\n const forwarded = useForwardPropsEmits(useOmit(props, ["class"]), emits);\r\n\r\n const localModel = useVModel(props, "modelValue", emits);\r\n\r\n const styles = tv({\r\n base: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-[16px] ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground file:hover:cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm",\r\n });\r\n</script>\r\n',
992
992
  },
993
993
  ],
994
994
  utils: [],
@@ -1022,7 +1022,7 @@ export default [
1022
1022
  fileName: "Label.vue",
1023
1023
  dirPath: "components/UI",
1024
1024
  fileContent:
1025
- '<template>\r\n <Label :class="styles({ class: props.class })" v-bind="forwarded">\r\n <slot />\r\n </Label>\r\n</template>\r\n\r\n<script lang="ts" setup>\r\n import { Label, useForwardProps } from "radix-vue";\r\n import type { LabelProps } from "radix-vue";\r\n\r\n const props = defineProps<\r\n LabelProps & {\r\n class?: any;\r\n }\r\n >();\r\n const forwarded = useForwardProps(useOmit(props, ["class"]));\r\n\r\n const styles = tv({\r\n base: "inline-block text-sm font-medium leading-none hover:cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70",\r\n });\r\n</script>\r\n',
1025
+ '<template>\r\n <Label :class="styles({ class: props.class })" v-bind="forwarded">\r\n <slot />\r\n </Label>\r\n</template>\r\n\r\n<script lang="ts" setup>\r\n import { Label, useForwardProps } from "radix-vue";\r\n import type { LabelProps } from "radix-vue";\r\n\r\n const props = defineProps<\r\n LabelProps & {\r\n class?: any;\r\n }\r\n >();\r\n const forwarded = useForwardProps(useOmit(props, ["class"]));\r\n\r\n const styles = tv({\r\n base: "inline-block text-base font-medium leading-none hover:cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70 sm:text-sm",\r\n });\r\n</script>\r\n',
1026
1026
  },
1027
1027
  ],
1028
1028
  utils: [],
@@ -1240,7 +1240,7 @@ export default [
1240
1240
  fileName: "OTP.vue",
1241
1241
  dirPath: "components/UI",
1242
1242
  fileContent:
1243
- '<template>\n <VOtpInput\n ref="otp"\n v-model:value="localModel"\n :input-classes="styles({ separator: Boolean(separator), class: inputClasses })"\n :separator="separator"\n :num-inputs="numInputs"\n :input-type="inputType"\n :inputmode="inputmode"\n :should-auto-focus="shouldAutoFocus"\n :placeholder="placeholder"\n :is-disabled="disabled"\n @on-change="emits(\'change\', $event)"\n @on-complete="emits(\'complete\', $event)"\n />\n</template>\n\n<script lang="ts" setup>\n import VOtpInput from "vue3-otp-input";\n\n const otp = ref<InstanceType<typeof VOtpInput> | null>(null);\n\n const props = withDefaults(\n defineProps<{\n modelValue?: string;\n numInputs?: number;\n separator?: string;\n inputClasses?: any;\n conditionalClass?: any[];\n inputType?: "number" | "tel" | "letter-numeric" | "password";\n inputmode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search";\n shouldAutoFocus?: boolean;\n placeholder?: string[];\n disabled?: boolean;\n }>(),\n {\n numInputs: 4,\n inputType: "letter-numeric",\n inputmode: "text",\n separator: "",\n }\n );\n const emits = defineEmits<{\n "update:modelValue": [any];\n change: [any];\n complete: [any];\n ready: [any];\n }>();\n const localModel = useVModel(props, "modelValue", emits);\n\n const styles = tv({\n base: "mr-3 h-10 w-10 rounded-md border border-input bg-background p-1 text-center font-medium [-moz-appearance:_textfield] selection:bg-primary selection:text-primary-foreground placeholder:text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none",\n variants: {\n separator: {\n true: "mx-2",\n },\n },\n });\n\n onMounted(() => {\n emits("ready", otp.value);\n });\n</script>\n',
1243
+ '<template>\n <VOtpInput\n ref="otp"\n v-model:value="localModel"\n :input-classes="styles({ separator: Boolean(separator), class: inputClasses })"\n :separator="separator"\n :num-inputs="numInputs"\n :input-type="inputType"\n :inputmode="inputmode"\n :should-auto-focus="shouldAutoFocus"\n :placeholder="placeholder"\n :is-disabled="disabled"\n @on-change="emits(\'change\', $event)"\n @on-complete="emits(\'complete\', $event)"\n />\n</template>\n\n<script lang="ts" setup>\n import VOtpInput from "vue3-otp-input";\n\n const otp = ref<InstanceType<typeof VOtpInput> | null>(null);\n\n const props = withDefaults(\n defineProps<{\n modelValue?: string;\n numInputs?: number;\n separator?: string;\n inputClasses?: any;\n conditionalClass?: any[];\n inputType?: "number" | "tel" | "letter-numeric" | "password";\n inputmode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search";\n shouldAutoFocus?: boolean;\n placeholder?: string[];\n disabled?: boolean;\n }>(),\n {\n numInputs: 4,\n inputType: "letter-numeric",\n inputmode: "text",\n separator: "",\n }\n );\n const emits = defineEmits<{\n "update:modelValue": [any];\n change: [any];\n complete: [any];\n ready: [any];\n }>();\n const localModel = useVModel(props, "modelValue", emits);\n\n const styles = tv({\n base: "mr-3 h-10 w-10 rounded-md border border-input bg-background p-1 text-center text-base font-medium [-moz-appearance:_textfield] selection:bg-primary selection:text-primary-foreground placeholder:text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none",\n variants: {\n separator: {\n true: "mx-2",\n },\n },\n });\n\n onMounted(() => {\n emits("ready", otp.value);\n });\n</script>\n',
1244
1244
  },
1245
1245
  ],
1246
1246
  utils: [],
@@ -1999,24 +1999,6 @@ export default [
1999
1999
  utils: [],
2000
2000
  composables: [],
2001
2001
  },
2002
- {
2003
- name: "Vue Sonner",
2004
- value: "vue-sonner",
2005
- deps: ["vue-sonner"],
2006
- devDeps: [],
2007
- nuxtModules: [],
2008
- instructions: ["Remember to add the <UIVueSonner /> tag to your app.vue/layout file."],
2009
- files: [
2010
- {
2011
- fileName: "VueSonner.client.vue",
2012
- dirPath: "components/UI",
2013
- fileContent:
2014
- '<template>\r\n <Toaster\r\n position="top-right"\r\n :visible-toasts="5"\r\n rich-colors\r\n :duration="7000"\r\n close-button\r\n :theme="$colorMode.value == \'dark\' ? \'dark\' : \'light\'"\r\n />\r\n</template>\r\n\r\n<script lang="ts" setup>\r\n import { Toaster } from "vue-sonner";\r\n</script>\r\n<style scoped>\r\n :deep([data-sonner-toaster][data-theme="dark"]),\r\n :deep([data-sonner-toaster][data-theme="light"]) {\r\n --normal-bg: theme("colors.popover.DEFAULT");\r\n --normal-border: theme("colors.border");\r\n --normal-text: theme("colors.popover.foreground");\r\n --border-radius: theme("borderRadius.md");\r\n }\r\n :deep([data-sonner-toaster]) {\r\n @apply font-sans;\r\n }\r\n :deep([data-sonner-toast][data-styled="true"]) {\r\n @apply items-start;\r\n }\r\n :deep([data-sonner-toast] [data-icon]) {\r\n @apply mt-0.5;\r\n }\r\n :deep([data-sonner-toast] [data-title]) {\r\n @apply text-sm font-semibold;\r\n }\r\n :deep([data-sonner-toast] [data-description]) {\r\n @apply text-sm;\r\n }\r\n :deep([data-sonner-toast] [data-close-button]) {\r\n @apply border border-border bg-background text-foreground hover:border-inherit hover:bg-inherit hover:text-accent-foreground;\r\n }\r\n :deep([data-sonner-toast] [data-button]) {\r\n @apply bg-primary text-primary-foreground transition hover:opacity-90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background;\r\n }\r\n :deep(.sonner-loading-bar) {\r\n @apply bg-muted-foreground;\r\n }\r\n</style>\r\n',
2015
- },
2016
- ],
2017
- utils: [],
2018
- composables: [],
2019
- },
2020
2002
  {
2021
2003
  name: "VeeCheckbox",
2022
2004
  value: "vue-checkbox",
@@ -2112,6 +2094,24 @@ export default [
2112
2094
  utils: [],
2113
2095
  composables: [],
2114
2096
  },
2097
+ {
2098
+ name: "Vue Sonner",
2099
+ value: "vue-sonner",
2100
+ deps: ["vue-sonner"],
2101
+ devDeps: [],
2102
+ nuxtModules: [],
2103
+ instructions: ["Remember to add the <UIVueSonner /> tag to your app.vue/layout file."],
2104
+ files: [
2105
+ {
2106
+ fileName: "VueSonner.client.vue",
2107
+ dirPath: "components/UI",
2108
+ fileContent:
2109
+ '<template>\r\n <Toaster\r\n position="top-right"\r\n :visible-toasts="5"\r\n rich-colors\r\n :duration="7000"\r\n close-button\r\n :theme="$colorMode.value == \'dark\' ? \'dark\' : \'light\'"\r\n />\r\n</template>\r\n\r\n<script lang="ts" setup>\r\n import { Toaster } from "vue-sonner";\r\n</script>\r\n<style scoped>\r\n :deep([data-sonner-toaster][data-theme="dark"]),\r\n :deep([data-sonner-toaster][data-theme="light"]) {\r\n --normal-bg: theme("colors.popover.DEFAULT");\r\n --normal-border: theme("colors.border");\r\n --normal-text: theme("colors.popover.foreground");\r\n --border-radius: theme("borderRadius.md");\r\n }\r\n :deep([data-sonner-toaster]) {\r\n @apply font-sans;\r\n }\r\n :deep([data-sonner-toast][data-styled="true"]) {\r\n @apply items-start;\r\n }\r\n :deep([data-sonner-toast] [data-icon]) {\r\n @apply mt-0.5;\r\n }\r\n :deep([data-sonner-toast] [data-title]) {\r\n @apply text-sm font-semibold;\r\n }\r\n :deep([data-sonner-toast] [data-description]) {\r\n @apply text-sm;\r\n }\r\n :deep([data-sonner-toast] [data-close-button]) {\r\n @apply border border-border bg-background text-foreground hover:border-inherit hover:bg-inherit hover:text-accent-foreground;\r\n }\r\n :deep([data-sonner-toast] [data-button]) {\r\n @apply bg-primary text-primary-foreground transition hover:opacity-90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background;\r\n }\r\n :deep(.sonner-loading-bar) {\r\n @apply bg-muted-foreground;\r\n }\r\n</style>\r\n',
2110
+ },
2111
+ ],
2112
+ utils: [],
2113
+ composables: [],
2114
+ },
2115
2115
  {
2116
2116
  name: "VeeTextarea",
2117
2117
  value: "vue-textarea",
package/src/types.ts CHANGED
@@ -11,3 +11,23 @@ export type UIConfig = {
11
11
  };
12
12
 
13
13
  export type InitOptions = { force?: boolean };
14
+
15
+ export type Component = {
16
+ name: string;
17
+ value: string;
18
+ deps: string[];
19
+ devDeps: string[];
20
+ nuxtModules: string[];
21
+ instructions?: string[];
22
+ files: Composable[];
23
+ utils: Composable[];
24
+ composables: Composable[];
25
+ components?: string[];
26
+ askValidator?: boolean;
27
+ };
28
+
29
+ export type Composable = {
30
+ fileName: string;
31
+ dirPath: string;
32
+ fileContent: string;
33
+ };
@@ -0,0 +1,78 @@
1
+ import fse from "fs-extra";
2
+ import * as prompts from "prompts";
3
+ import * as execa from "execa";
4
+ import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
5
+
6
+ import * as testingFn from "../../src/utils/addPrettierConfig";
7
+
8
+ const currentDir = process.cwd();
9
+ const question = "A prettier config file already exists. Overwrite?";
10
+ const promptOptions = {
11
+ name: "overwrite",
12
+ type: "confirm",
13
+ message: question,
14
+ initial: true,
15
+ };
16
+
17
+ describe("utils/addPrettierConfig", () => {
18
+ afterEach(() => {
19
+ vi.restoreAllMocks();
20
+ });
21
+
22
+ it(
23
+ "should ask the user if they want to overwrite the existing prettier config file if one exists",
24
+ async () => {
25
+ vi.spyOn(fse, "existsSync").mockImplementation(() => true);
26
+ vi.mock('prompts', async () => {
27
+ const prompts = await import('prompts');
28
+ return {
29
+ ...prompts,
30
+ default: async () => {
31
+ return { overwrite: false };
32
+ }
33
+ }
34
+ });
35
+
36
+ const result = await testingFn.addPrettierConfig();
37
+ expect(result).toBe(false);
38
+ expect(fse.existsSync).toHaveBeenCalledTimes(1);
39
+ }
40
+ );
41
+
42
+ it('should create config file if one does not exist', async () => {
43
+ vi.spyOn(fse, "existsSync").mockImplementation(() => false);
44
+ vi.spyOn(fse, "writeFile").mockResolvedValue();
45
+
46
+ const result = await testingFn.addPrettierConfig(currentDir, false);
47
+ expect(result).toBe(true);
48
+ expect(fse.existsSync).toHaveBeenCalledTimes(1);
49
+ expect(fse.writeFile).toHaveBeenCalledTimes(1);
50
+ })
51
+
52
+ it('should format files with prettier if format is true', async () => {
53
+ vi.spyOn(execa, '$')
54
+ vi.spyOn(testingFn, 'addPrettierConfig')
55
+ vi.spyOn(fse, "existsSync").mockImplementation(() => false);
56
+ vi.spyOn(fse, "writeFile").mockResolvedValue();
57
+ vi.mock('execa', async () => {
58
+ const execa = await import('execa');
59
+ return {
60
+ ...execa,
61
+ '$': async () => {
62
+ return true
63
+ },
64
+ default: async () => {
65
+ return true
66
+ }
67
+ }
68
+ });
69
+
70
+ const result = await testingFn.addPrettierConfig(currentDir, true);
71
+ expect(result).toBe(true);
72
+ expect(fse.existsSync).toHaveBeenCalledTimes(1);
73
+ expect(fse.writeFile).toHaveBeenCalledTimes(1);
74
+ expect(execa.$).toHaveBeenCalledTimes(1);
75
+ expect(testingFn.addPrettierConfig).toHaveBeenCalledTimes(1);
76
+
77
+ })
78
+ });
@@ -0,0 +1,38 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+
5
+ import * as testingFn from "../../src/utils/fileExists";
6
+
7
+ const currentDir = process.cwd();
8
+ const badPath = path.join(currentDir, "test");
9
+ const goodPath = path.join(currentDir, "tests/utils/fileExists.test.ts");
10
+
11
+ describe("utils/fileExists", () => {
12
+ afterEach(() => {
13
+ vi.restoreAllMocks();
14
+ });
15
+ it("should return true if file exists", async () => {
16
+ const spy = vi.spyOn(fs.promises, "access");
17
+ const fnSpy = vi.spyOn(testingFn, "fileExists");
18
+
19
+ const exists = await testingFn.fileExists(goodPath);
20
+ expect(fnSpy).toHaveBeenCalledWith(goodPath);
21
+ expect(spy).toHaveBeenCalledWith(goodPath, fs.constants.F_OK || fs.constants.W_OK);
22
+ expect(fnSpy).toBeCalledTimes(1);
23
+ expect(spy).toBeCalledTimes(1);
24
+ expect(exists).toBe(true);
25
+ });
26
+
27
+ it("should return false if file does not exist", async () => {
28
+ const spy = vi.spyOn(fs.promises, "access");
29
+ const fnSpy = vi.spyOn(testingFn, "fileExists");
30
+
31
+ const exists = await testingFn.fileExists(badPath);
32
+ expect(fnSpy).toHaveBeenCalledWith(badPath);
33
+ expect(spy).toHaveBeenCalledWith(badPath, fs.constants.F_OK || fs.constants.W_OK);
34
+ expect(fnSpy).toBeCalledTimes(1);
35
+ expect(spy).toBeCalledTimes(1);
36
+ expect(exists).toBe(false);
37
+ });
38
+ });
package/vite.config.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ coverage: {
6
+ provider: "v8",
7
+ },
8
+ },
9
+ });