@jsdev_ninja/core 0.12.5 → 0.12.7
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/dist/core.cjs.js +1 -1
- package/dist/core.cjs.js.map +1 -1
- package/dist/core.es.js +53 -53
- package/dist/core.es.js.map +1 -1
- package/dist/core.umd.js +1 -1
- package/dist/core.umd.js.map +1 -1
- package/dist/entities/Store.d.ts +2 -2
- package/dist/entities/Store.d.ts.map +1 -1
- package/dist/entities/Store.js +2 -2
- package/lib/entities/Discount/__tests__/engine.test.ts +337 -0
- package/lib/entities/Discount/__tests__/factory.test.ts +91 -0
- package/lib/entities/Discount/__tests__/integration.test.ts +132 -0
- package/lib/entities/Discount/__tests__/simple.test.ts +215 -0
- package/lib/entities/Discount/__tests__/utils.test.ts +69 -0
- package/lib/entities/Discount/engine.ts +129 -0
- package/lib/entities/Discount/factory.ts +25 -0
- package/lib/entities/Discount/index.ts +10 -0
- package/lib/entities/Discount/strategies/BundleStrategy.ts +123 -0
- package/lib/entities/Discount/strategies/__tests__/BundleStrategy.test.ts +290 -0
- package/lib/entities/Discount/strategy.ts +6 -0
- package/lib/entities/Discount/types.ts +68 -0
- package/lib/entities/Discount/utils.ts +42 -0
- package/lib/entities/Store.ts +4 -2
- package/lib/utils/index.ts +3 -0
- package/package.json +10 -5
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { BundleDiscountStrategy } from "../BundleStrategy";
|
|
3
|
+
import { TDiscount, DiscountContext } from "../../types";
|
|
4
|
+
|
|
5
|
+
describe("BundleDiscountStrategy", () => {
|
|
6
|
+
let strategy: BundleDiscountStrategy;
|
|
7
|
+
let mockContext: DiscountContext;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
strategy = new BundleDiscountStrategy();
|
|
11
|
+
|
|
12
|
+
mockContext = {
|
|
13
|
+
cart: [
|
|
14
|
+
{
|
|
15
|
+
amount: 2,
|
|
16
|
+
product: {
|
|
17
|
+
id: "product1",
|
|
18
|
+
price: 10,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
amount: 1,
|
|
23
|
+
product: {
|
|
24
|
+
id: "product2",
|
|
25
|
+
price: 15,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
user: undefined,
|
|
30
|
+
appliedDiscounts: [],
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("canApply", () => {
|
|
35
|
+
it("should return true for valid bundle discount", () => {
|
|
36
|
+
const discount: TDiscount = {
|
|
37
|
+
type: "Discount",
|
|
38
|
+
storeId: "store1",
|
|
39
|
+
companyId: "company1",
|
|
40
|
+
id: "bundle1",
|
|
41
|
+
name: [{ lang: "he", value: "Buy 3 for $25" }],
|
|
42
|
+
active: true,
|
|
43
|
+
startDate: Date.now() - 1000,
|
|
44
|
+
endDate: Date.now() + 1000,
|
|
45
|
+
variant: {
|
|
46
|
+
variantType: "bundle",
|
|
47
|
+
productsId: ["product1", "product2"],
|
|
48
|
+
requiredQuantity: 3,
|
|
49
|
+
bundlePrice: 25,
|
|
50
|
+
},
|
|
51
|
+
conditions: {
|
|
52
|
+
stackable: false,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
expect(strategy.canApply(discount, mockContext)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should return false for wrong variant type", () => {
|
|
60
|
+
const discount = {
|
|
61
|
+
...mockDiscount,
|
|
62
|
+
variant: {
|
|
63
|
+
variantType: "percentage" as any,
|
|
64
|
+
percentage: 20,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
expect(strategy.canApply(discount, mockContext)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should return false when not enough items", () => {
|
|
72
|
+
const discount: TDiscount = {
|
|
73
|
+
type: "Discount",
|
|
74
|
+
storeId: "store1",
|
|
75
|
+
companyId: "company1",
|
|
76
|
+
id: "bundle1",
|
|
77
|
+
name: [{ lang: "he", value: "Buy 5 for $40" }],
|
|
78
|
+
active: true,
|
|
79
|
+
startDate: Date.now() - 1000,
|
|
80
|
+
endDate: Date.now() + 1000,
|
|
81
|
+
variant: {
|
|
82
|
+
variantType: "bundle",
|
|
83
|
+
productsId: ["product1", "product2"],
|
|
84
|
+
requiredQuantity: 5, // Need 5, but cart only has 3
|
|
85
|
+
bundlePrice: 40,
|
|
86
|
+
},
|
|
87
|
+
conditions: {
|
|
88
|
+
stackable: false,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
expect(strategy.canApply(discount, mockContext)).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should return false when discount is inactive", () => {
|
|
96
|
+
const discount: TDiscount = {
|
|
97
|
+
type: "Discount",
|
|
98
|
+
storeId: "store1",
|
|
99
|
+
companyId: "company1",
|
|
100
|
+
id: "bundle1",
|
|
101
|
+
name: [{ lang: "he", value: "Buy 3 for $25" }],
|
|
102
|
+
active: false, // Inactive
|
|
103
|
+
startDate: Date.now() - 1000,
|
|
104
|
+
endDate: Date.now() + 1000,
|
|
105
|
+
variant: {
|
|
106
|
+
variantType: "bundle",
|
|
107
|
+
productsId: ["product1", "product2"],
|
|
108
|
+
requiredQuantity: 3,
|
|
109
|
+
bundlePrice: 25,
|
|
110
|
+
},
|
|
111
|
+
conditions: {
|
|
112
|
+
stackable: false,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
expect(strategy.canApply(discount, mockContext)).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should return false when discount is expired", () => {
|
|
120
|
+
const discount: TDiscount = {
|
|
121
|
+
type: "Discount",
|
|
122
|
+
storeId: "store1",
|
|
123
|
+
companyId: "company1",
|
|
124
|
+
id: "bundle1",
|
|
125
|
+
name: [{ lang: "he", value: "Buy 3 for $25" }],
|
|
126
|
+
active: true,
|
|
127
|
+
startDate: Date.now() - 2000,
|
|
128
|
+
endDate: Date.now() - 1000, // Expired
|
|
129
|
+
variant: {
|
|
130
|
+
variantType: "bundle",
|
|
131
|
+
productsId: ["product1", "product2"],
|
|
132
|
+
requiredQuantity: 3,
|
|
133
|
+
bundlePrice: 25,
|
|
134
|
+
},
|
|
135
|
+
conditions: {
|
|
136
|
+
stackable: false,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
expect(strategy.canApply(discount, mockContext)).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("calculate", () => {
|
|
145
|
+
it("should calculate bundle discount correctly", () => {
|
|
146
|
+
const discount: TDiscount = {
|
|
147
|
+
type: "Discount",
|
|
148
|
+
storeId: "store1",
|
|
149
|
+
companyId: "company1",
|
|
150
|
+
id: "bundle1",
|
|
151
|
+
name: [{ lang: "he", value: "Buy 3 for $25" }],
|
|
152
|
+
active: true,
|
|
153
|
+
startDate: Date.now() - 1000,
|
|
154
|
+
endDate: Date.now() + 1000,
|
|
155
|
+
variant: {
|
|
156
|
+
variantType: "bundle",
|
|
157
|
+
productsId: ["product1", "product2"],
|
|
158
|
+
requiredQuantity: 3,
|
|
159
|
+
bundlePrice: 25,
|
|
160
|
+
},
|
|
161
|
+
conditions: {
|
|
162
|
+
stackable: false,
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const result = strategy.calculate(discount, mockContext);
|
|
167
|
+
|
|
168
|
+
expect(result.applicable).toBe(true);
|
|
169
|
+
expect(result.discountAmount).toBe(10); // (2×10 + 1×15) - 25 = 35 - 25 = 10
|
|
170
|
+
expect(result.affectedItems).toHaveLength(2);
|
|
171
|
+
|
|
172
|
+
// Check product1 discount
|
|
173
|
+
const product1Item = result.affectedItems.find(item => item.productId === "product1");
|
|
174
|
+
expect(product1Item).toBeDefined();
|
|
175
|
+
expect(product1Item!.discountAmount).toBeCloseTo(5.71, 2); // Proportional to 20/35
|
|
176
|
+
|
|
177
|
+
// Check product2 discount
|
|
178
|
+
const product2Item = result.affectedItems.find(item => item.productId === "product2");
|
|
179
|
+
expect(product2Item).toBeDefined();
|
|
180
|
+
expect(product2Item!.discountAmount).toBeCloseTo(4.29, 2); // Proportional to 15/35
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should handle multiple bundles", () => {
|
|
184
|
+
const contextWithMoreItems: DiscountContext = {
|
|
185
|
+
cart: [
|
|
186
|
+
{
|
|
187
|
+
amount: 6, // 6 items
|
|
188
|
+
product: {
|
|
189
|
+
id: "product1",
|
|
190
|
+
price: 10,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
user: undefined,
|
|
195
|
+
appliedDiscounts: [],
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const discount: TDiscount = {
|
|
199
|
+
type: "Discount",
|
|
200
|
+
storeId: "store1",
|
|
201
|
+
companyId: "company1",
|
|
202
|
+
id: "bundle1",
|
|
203
|
+
name: [{ lang: "he", value: "Buy 3 for $25" }],
|
|
204
|
+
active: true,
|
|
205
|
+
startDate: Date.now() - 1000,
|
|
206
|
+
endDate: Date.now() + 1000,
|
|
207
|
+
variant: {
|
|
208
|
+
variantType: "bundle",
|
|
209
|
+
productsId: ["product1"],
|
|
210
|
+
requiredQuantity: 3,
|
|
211
|
+
bundlePrice: 25,
|
|
212
|
+
},
|
|
213
|
+
conditions: {
|
|
214
|
+
stackable: false,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const result = strategy.calculate(discount, contextWithMoreItems);
|
|
219
|
+
|
|
220
|
+
expect(result.applicable).toBe(true);
|
|
221
|
+
expect(result.discountAmount).toBe(10); // (6×10) - (2×25) = 60 - 50 = 10
|
|
222
|
+
expect(result.affectedItems).toHaveLength(1);
|
|
223
|
+
expect(result.affectedItems[0].discountAmount).toBe(10);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("should return not applicable for wrong variant type", () => {
|
|
227
|
+
const discount = {
|
|
228
|
+
...mockDiscount,
|
|
229
|
+
variant: {
|
|
230
|
+
variantType: "percentage" as any,
|
|
231
|
+
percentage: 20,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const result = strategy.calculate(discount, mockContext);
|
|
236
|
+
|
|
237
|
+
expect(result.applicable).toBe(false);
|
|
238
|
+
expect(result.discountAmount).toBe(0);
|
|
239
|
+
expect(result.affectedItems).toHaveLength(0);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("should return not applicable when not enough items", () => {
|
|
243
|
+
const discount: TDiscount = {
|
|
244
|
+
type: "Discount",
|
|
245
|
+
storeId: "store1",
|
|
246
|
+
companyId: "company1",
|
|
247
|
+
id: "bundle1",
|
|
248
|
+
name: [{ lang: "he", value: "Buy 5 for $40" }],
|
|
249
|
+
active: true,
|
|
250
|
+
startDate: Date.now() - 1000,
|
|
251
|
+
endDate: Date.now() + 1000,
|
|
252
|
+
variant: {
|
|
253
|
+
variantType: "bundle",
|
|
254
|
+
productsId: ["product1", "product2"],
|
|
255
|
+
requiredQuantity: 5,
|
|
256
|
+
bundlePrice: 40,
|
|
257
|
+
},
|
|
258
|
+
conditions: {
|
|
259
|
+
stackable: false,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const result = strategy.calculate(discount, mockContext);
|
|
264
|
+
|
|
265
|
+
expect(result.applicable).toBe(false);
|
|
266
|
+
expect(result.discountAmount).toBe(0);
|
|
267
|
+
expect(result.affectedItems).toHaveLength(0);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const mockDiscount: TDiscount = {
|
|
273
|
+
type: "Discount",
|
|
274
|
+
storeId: "store1",
|
|
275
|
+
companyId: "company1",
|
|
276
|
+
id: "bundle1",
|
|
277
|
+
name: [{ lang: "he", value: "Buy 3 for $25" }],
|
|
278
|
+
active: true,
|
|
279
|
+
startDate: Date.now() - 1000,
|
|
280
|
+
endDate: Date.now() + 1000,
|
|
281
|
+
variant: {
|
|
282
|
+
variantType: "bundle",
|
|
283
|
+
productsId: ["product1", "product2"],
|
|
284
|
+
requiredQuantity: 3,
|
|
285
|
+
bundlePrice: 25,
|
|
286
|
+
},
|
|
287
|
+
conditions: {
|
|
288
|
+
stackable: false,
|
|
289
|
+
},
|
|
290
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// Simple discount conditions
|
|
4
|
+
export const DiscountConditionsSchema = z.object({
|
|
5
|
+
minSpend: z.number().positive().optional(),
|
|
6
|
+
stackable: z.boolean().default(false),
|
|
7
|
+
}).optional();
|
|
8
|
+
|
|
9
|
+
// Bundle discount - buy X items for Y price
|
|
10
|
+
export const DiscountVariantSchema = z.discriminatedUnion("variantType", [
|
|
11
|
+
z.object({
|
|
12
|
+
variantType: z.literal("bundle"),
|
|
13
|
+
productsId: z.array(z.string()).min(1), // Which products are included
|
|
14
|
+
requiredQuantity: z.number().positive(), // How many items needed (e.g., 3)
|
|
15
|
+
bundlePrice: z.number().positive(), // Total price for the bundle (e.g., $25)
|
|
16
|
+
}),
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export const DiscountSchema = z.object({
|
|
20
|
+
type: z.literal("Discount"),
|
|
21
|
+
storeId: z.string().min(1),
|
|
22
|
+
companyId: z.string().min(1),
|
|
23
|
+
id: z.string().min(1),
|
|
24
|
+
name: z.array(z.object({ lang: z.enum(["he"]), value: z.string() })),
|
|
25
|
+
active: z.boolean(),
|
|
26
|
+
startDate: z.number(),
|
|
27
|
+
endDate: z.number(),
|
|
28
|
+
variant: DiscountVariantSchema,
|
|
29
|
+
conditions: DiscountConditionsSchema,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export type TDiscount = z.infer<typeof DiscountSchema>;
|
|
33
|
+
export type DiscountVariant = z.infer<typeof DiscountVariantSchema>;
|
|
34
|
+
export type DiscountConditions = z.infer<typeof DiscountConditionsSchema>;
|
|
35
|
+
|
|
36
|
+
// Simple result types
|
|
37
|
+
export interface DiscountResult {
|
|
38
|
+
applicable: boolean;
|
|
39
|
+
discountAmount: number;
|
|
40
|
+
affectedItems: Array<{
|
|
41
|
+
productId: string;
|
|
42
|
+
quantity: number;
|
|
43
|
+
originalPrice: number;
|
|
44
|
+
discountedPrice: number;
|
|
45
|
+
discountAmount: number;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface AppliedDiscount {
|
|
50
|
+
discountId: string;
|
|
51
|
+
discountName: string;
|
|
52
|
+
discountAmount: number;
|
|
53
|
+
affectedItems: DiscountResult["affectedItems"];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface DiscountContext {
|
|
57
|
+
cart: Array<{
|
|
58
|
+
amount: number;
|
|
59
|
+
product: {
|
|
60
|
+
id: string;
|
|
61
|
+
price: number;
|
|
62
|
+
};
|
|
63
|
+
}>;
|
|
64
|
+
user?: Record<string, unknown>;
|
|
65
|
+
appliedDiscounts: AppliedDiscount[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for discount calculations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Formats a number to 2 decimal places and returns as a number
|
|
7
|
+
* @param value - The number to format
|
|
8
|
+
* @returns The formatted number with 2 decimal places
|
|
9
|
+
*/
|
|
10
|
+
export function formatCurrency(value: number): number {
|
|
11
|
+
return Number(value.toFixed(2));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Formats a number to 2 decimal places and returns as a string
|
|
16
|
+
* @param value - The number to format
|
|
17
|
+
* @returns The formatted string with 2 decimal places
|
|
18
|
+
*/
|
|
19
|
+
export function formatCurrencyString(value: number): string {
|
|
20
|
+
return value.toFixed(2);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Ensures a monetary value is never negative
|
|
25
|
+
* @param value - The value to check
|
|
26
|
+
* @returns The value, or 0 if negative
|
|
27
|
+
*/
|
|
28
|
+
export function ensureNonNegative(value: number): number {
|
|
29
|
+
return Math.max(0, formatCurrency(value));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Calculates the percentage discount
|
|
34
|
+
* @param originalPrice - The original price
|
|
35
|
+
* @param discountedPrice - The discounted price
|
|
36
|
+
* @returns The percentage discount (0-100)
|
|
37
|
+
*/
|
|
38
|
+
export function calculatePercentageDiscount(originalPrice: number, discountedPrice: number): number {
|
|
39
|
+
if (originalPrice <= 0) return 0;
|
|
40
|
+
const discount = originalPrice - discountedPrice;
|
|
41
|
+
return formatCurrency((discount / originalPrice) * 100);
|
|
42
|
+
}
|
package/lib/entities/Store.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
const clientTypesSchema = z.enum(["individual", "company"]);
|
|
4
|
-
|
|
3
|
+
export const clientTypesSchema = z.enum(["individual", "company"]);
|
|
4
|
+
|
|
5
|
+
export const StoreSchema = z.object({
|
|
5
6
|
id: z.string(),
|
|
6
7
|
companyId: z.string(),
|
|
7
8
|
name: z.string(),
|
|
@@ -16,4 +17,5 @@ const StoreSchema = z.object({
|
|
|
16
17
|
freeDeliveryPrice: z.number().optional(),
|
|
17
18
|
deliveryPrice: z.number().optional(),
|
|
18
19
|
});
|
|
20
|
+
|
|
19
21
|
export type TStore = z.infer<typeof StoreSchema>;
|
package/lib/utils/index.ts
CHANGED
|
@@ -51,6 +51,9 @@ export function getCartCost({
|
|
|
51
51
|
};
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
// calculate delivery price before
|
|
55
|
+
// check if cart cost is greater than free delivery price
|
|
56
|
+
|
|
54
57
|
const activeDiscounts = discounts.filter((discount) => {
|
|
55
58
|
if (discount.variant.variantType === "bundle") {
|
|
56
59
|
const productsTotal =
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsdev_ninja/core",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.7",
|
|
4
4
|
"main": "dist/core.cjs.js",
|
|
5
5
|
"module": "dist/core.es.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,15 +12,16 @@
|
|
|
12
12
|
"build": "vite build && tsc -b",
|
|
13
13
|
"lint": "eslint .",
|
|
14
14
|
"preview": "vite preview",
|
|
15
|
-
"publish_core": "yarn publish --non-interactive"
|
|
15
|
+
"publish_core": "yarn publish --non-interactive",
|
|
16
|
+
"test": "vitest",
|
|
17
|
+
"test:run": "vitest run"
|
|
16
18
|
},
|
|
17
19
|
"dependencies": {
|
|
18
20
|
"zod": "^3.23.8"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
21
|
-
"react": "^18.3.1",
|
|
22
|
-
"react-dom": "^18.3.1",
|
|
23
23
|
"@eslint/js": "^9.9.0",
|
|
24
|
+
"@types/node": "^24.0.15",
|
|
24
25
|
"@types/react": "^18.3.3",
|
|
25
26
|
"@types/react-dom": "^18.3.0",
|
|
26
27
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
|
@@ -28,8 +29,12 @@
|
|
|
28
29
|
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
|
29
30
|
"eslint-plugin-react-refresh": "^0.4.9",
|
|
30
31
|
"globals": "^15.9.0",
|
|
32
|
+
"react": "^18.3.1",
|
|
33
|
+
"react-dom": "^18.3.1",
|
|
34
|
+
"ts-node": "^10.9.2",
|
|
31
35
|
"typescript": "^5.5.3",
|
|
32
36
|
"typescript-eslint": "^8.0.1",
|
|
33
|
-
"vite": "^5.4.1"
|
|
37
|
+
"vite": "^5.4.1",
|
|
38
|
+
"vitest": "^3.2.4"
|
|
34
39
|
}
|
|
35
40
|
}
|
package/vitest.config.ts
ADDED