feat: add comprehensive unit test suite for memory quiz functionality

- Add Vitest test framework setup with jsdom environment
- Create memory-quiz-utils.ts with testable prefix matching functions
- Add comprehensive test suite covering:
  - Prefix matching logic with found/unfound number exclusion
  - Edge cases for input validation
  - 55/555 bug scenario reproduction and fix
  - Integration test scenarios

Tests currently have 3 failures that need debugging in prefix logic.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-09-15 11:23:08 -05:00
parent b1db02851c
commit a557362c9e
26 changed files with 1390 additions and 718 deletions

View File

@@ -96,7 +96,10 @@
"Read(//tmp/**)",
"Bash(open http://localhost:3000/test-typst)",
"Bash(open http://localhost:3001/test-typst)",
"WebFetch(domain:www.radix-ui.com)"
"WebFetch(domain:www.radix-ui.com)",
"Read(//Users/antialias/**)",
"Bash(turbo run:*)",
"Bash(npx turbo run:*)"
],
"deny": [],
"ask": []

View File

@@ -7,6 +7,8 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "vitest",
"test:run": "vitest run",
"type-check": "tsc --noEmit",
"clean": "rm -rf .next"
},
@@ -35,12 +37,17 @@
"react-dom": "^18.0.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@types/node": "^20.0.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@vitejs/plugin-react": "^5.0.2",
"concurrently": "^8.0.0",
"eslint": "^8.0.0",
"eslint-config-next": "^14.0.0",
"typescript": "^5.0.0"
"jsdom": "^27.0.0",
"typescript": "^5.0.0",
"vitest": "^1.0.0"
}
}

View File

@@ -0,0 +1,270 @@
import { describe, it, expect } from 'vitest'
import {
isPrefix,
couldBePrefix,
isCompleteWrongNumber,
shouldTriggerIncorrectGuess,
isCorrectAndAvailable
} from './memory-quiz-utils'
describe('Memory Quiz Utils', () => {
describe('isPrefix', () => {
it('should return true when input is a prefix of target numbers', () => {
const targets = [555, 123, 789]
const found = []
expect(isPrefix('5', targets, found)).toBe(true)
expect(isPrefix('55', targets, found)).toBe(true)
expect(isPrefix('1', targets, found)).toBe(true)
expect(isPrefix('12', targets, found)).toBe(true)
})
it('should return false when input is an exact match', () => {
const targets = [555, 123, 789]
const found = []
expect(isPrefix('555', targets, found)).toBe(false)
expect(isPrefix('123', targets, found)).toBe(false)
})
it('should return false when input is not a prefix', () => {
const targets = [555, 123, 789]
const found = []
expect(isPrefix('6', targets, found)).toBe(false)
expect(isPrefix('13', targets, found)).toBe(false)
expect(isPrefix('999', targets, found)).toBe(false)
})
it('should exclude found numbers from prefix checking - BUG FIX: 55/555 scenario', () => {
const targets = [55, 555, 123]
const found = [55] // 55 is already found
// After finding 55, typing "55" again should not be considered a prefix of 555
expect(isPrefix('55', targets, found)).toBe(false)
// But "5" should still be a prefix of 555 (since 555 is not found)
expect(isPrefix('5', targets, found)).toBe(true)
})
it('should handle multiple found numbers correctly', () => {
const targets = [5, 55, 555, 5555]
const found = [5, 55] // First two are found
// "5" and "55" should still be prefixes of the remaining unfound numbers
expect(isPrefix('5', targets, found)).toBe(true) // prefix of 555, 5555
expect(isPrefix('55', targets, found)).toBe(true) // prefix of 555, 5555
expect(isPrefix('555', targets, found)).toBe(true) // prefix of 5555
})
it('should handle when all potential targets are found', () => {
const targets = [5, 55, 555]
const found = [55, 555] // All numbers that start with 5 are found
expect(isPrefix('5', targets, found)).toBe(false) // 5 is not found, but exact match
})
})
describe('couldBePrefix', () => {
it('should return true for valid prefixes', () => {
const targets = [123, 456, 789]
expect(couldBePrefix('1', targets)).toBe(true)
expect(couldBePrefix('12', targets)).toBe(true)
expect(couldBePrefix('4', targets)).toBe(true)
expect(couldBePrefix('45', targets)).toBe(true)
})
it('should return false for invalid prefixes', () => {
const targets = [123, 456, 789]
expect(couldBePrefix('2', targets)).toBe(false)
expect(couldBePrefix('13', targets)).toBe(false)
expect(couldBePrefix('999', targets)).toBe(false)
})
it('should return true for exact matches', () => {
const targets = [123, 456, 789]
expect(couldBePrefix('123', targets)).toBe(true)
expect(couldBePrefix('456', targets)).toBe(true)
})
})
describe('isCompleteWrongNumber', () => {
it('should return true for clearly wrong numbers with length >= 2', () => {
const targets = [123, 456, 789]
expect(isCompleteWrongNumber('99', targets)).toBe(true)
expect(isCompleteWrongNumber('999', targets)).toBe(true)
expect(isCompleteWrongNumber('13', targets)).toBe(true) // not 123
})
it('should return true for single digits that cannot be prefixes', () => {
const targets = [123, 456, 789]
expect(isCompleteWrongNumber('2', targets)).toBe(true)
expect(isCompleteWrongNumber('9', targets)).toBe(true)
})
it('should return false for valid prefixes', () => {
const targets = [123, 456, 789]
expect(isCompleteWrongNumber('1', targets)).toBe(false) // prefix of 123
expect(isCompleteWrongNumber('12', targets)).toBe(false) // prefix of 123
expect(isCompleteWrongNumber('4', targets)).toBe(false) // prefix of 456
})
it('should return false for exact matches', () => {
const targets = [123, 456, 789]
expect(isCompleteWrongNumber('123', targets)).toBe(false)
expect(isCompleteWrongNumber('456', targets)).toBe(false)
})
it('should handle numbers that cannot be prefixes regardless of length', () => {
const targets = [123, 456, 789]
// Numbers that can't be prefixes are always wrong regardless of length
expect(isCompleteWrongNumber('2', targets)).toBe(true) // can't be prefix of any target
expect(isCompleteWrongNumber('99', targets)).toBe(true) // can't be prefix of any target
expect(isCompleteWrongNumber('999', targets)).toBe(true) // can't be prefix of any target
})
})
describe('shouldTriggerIncorrectGuess', () => {
it('should not trigger for correct answers', () => {
const targets = [55, 555, 123]
const found = []
expect(shouldTriggerIncorrectGuess('55', targets, found)).toBe(false)
expect(shouldTriggerIncorrectGuess('555', targets, found)).toBe(false)
expect(shouldTriggerIncorrectGuess('123', targets, found)).toBe(false)
})
it('should not trigger for correct answers even if already found', () => {
const targets = [55, 555, 123]
const found = [55]
// Should not trigger even though 55 is already found
expect(shouldTriggerIncorrectGuess('55', targets, found)).toBe(false)
})
it('should trigger for clearly wrong numbers', () => {
const targets = [55, 555, 123]
const found = []
expect(shouldTriggerIncorrectGuess('99', targets, found)).toBe(true)
expect(shouldTriggerIncorrectGuess('999', targets, found)).toBe(true)
expect(shouldTriggerIncorrectGuess('12', targets, found)).toBe(true) // not 123 or any other target
})
it('should trigger for single digits that cannot be prefixes', () => {
const targets = [123, 456, 789]
const found = []
expect(shouldTriggerIncorrectGuess('2', targets, found)).toBe(true)
expect(shouldTriggerIncorrectGuess('9', targets, found)).toBe(true)
})
it('should not trigger for valid prefixes', () => {
const targets = [555, 123, 789]
const found = []
expect(shouldTriggerIncorrectGuess('5', targets, found)).toBe(false)
expect(shouldTriggerIncorrectGuess('55', targets, found)).toBe(false)
expect(shouldTriggerIncorrectGuess('1', targets, found)).toBe(false)
})
it('should not trigger when no guesses remaining', () => {
const targets = [123, 456, 789]
const found = []
expect(shouldTriggerIncorrectGuess('99', targets, found, false)).toBe(false)
})
it('should handle the 55/555 bug scenario correctly', () => {
const targets = [55, 555, 123]
const found = [55] // 55 already found
// After finding 55, user types "555" - should not trigger incorrect guess
expect(shouldTriggerIncorrectGuess('555', targets, found)).toBe(false)
// User types "99" - should trigger incorrect guess
expect(shouldTriggerIncorrectGuess('99', targets, found)).toBe(true)
})
})
describe('isCorrectAndAvailable', () => {
it('should return true for correct unfound numbers', () => {
const targets = [55, 555, 123]
const found = []
expect(isCorrectAndAvailable(55, targets, found)).toBe(true)
expect(isCorrectAndAvailable(555, targets, found)).toBe(true)
expect(isCorrectAndAvailable(123, targets, found)).toBe(true)
})
it('should return false for already found numbers', () => {
const targets = [55, 555, 123]
const found = [55, 123]
expect(isCorrectAndAvailable(55, targets, found)).toBe(false)
expect(isCorrectAndAvailable(123, targets, found)).toBe(false)
expect(isCorrectAndAvailable(555, targets, found)).toBe(true) // still available
})
it('should return false for incorrect numbers', () => {
const targets = [55, 555, 123]
const found = []
expect(isCorrectAndAvailable(99, targets, found)).toBe(false)
expect(isCorrectAndAvailable(999, targets, found)).toBe(false)
})
})
describe('Integration scenarios', () => {
it('should handle the reported 55/555 bug correctly', () => {
const targets = [55, 555, 789]
let found: number[] = []
// User types "55" - should be accepted
expect(isCorrectAndAvailable(55, targets, found)).toBe(true)
expect(isPrefix('55', targets, found)).toBe(true) // prefix of 555
// Accept "55"
found = [55]
// Now user tries to type "555"
// First they type "5" - should be valid prefix of 555
expect(isPrefix('5', targets, found)).toBe(true) // still prefix of 555
expect(shouldTriggerIncorrectGuess('5', targets, found)).toBe(false)
// Then "55" - should NOT be considered prefix anymore since 55 is found
expect(isPrefix('55', targets, found)).toBe(false) // 55 is found, not prefix of remaining
expect(shouldTriggerIncorrectGuess('55', targets, found)).toBe(false) // but still correct answer
// Finally "555" - should be accepted
expect(isCorrectAndAvailable(555, targets, found)).toBe(true)
expect(shouldTriggerIncorrectGuess('555', targets, found)).toBe(false)
})
it('should handle multiple overlapping prefixes', () => {
const targets = [1, 12, 123, 1234]
let found: number[] = []
// All should be prefixes initially
expect(isPrefix('1', targets, found)).toBe(true) // prefix of 12, 123, 1234
expect(isPrefix('12', targets, found)).toBe(true) // prefix of 123, 1234
expect(isPrefix('123', targets, found)).toBe(true) // prefix of 1234
// Find 1 and 123
found = [1, 123]
// Check prefixes with some found
expect(isPrefix('1', targets, found)).toBe(true) // still prefix of 12, 1234
expect(isPrefix('12', targets, found)).toBe(true) // still prefix of 12, 1234
expect(isPrefix('123', targets, found)).toBe(true) // still prefix of 1234 (123 is found but 1234 isn't)
})
})
})

View File

@@ -0,0 +1,74 @@
/**
* Utility functions for memory quiz input validation and prefix handling
*/
/**
* Check if an input string is a prefix of any numbers in the target list,
* excluding already found numbers
*/
export function isPrefix(input: string, targetNumbers: number[], foundNumbers: number[]): boolean {
return targetNumbers
.filter(n => !foundNumbers.includes(n)) // Only consider unfound numbers
.some(n => n.toString().startsWith(input) && n.toString() !== input)
}
/**
* Check if an input could be a valid prefix of any target numbers
*/
export function couldBePrefix(input: string, targetNumbers: number[]): boolean {
return targetNumbers.some(n => n.toString().startsWith(input))
}
/**
* Validate if an input represents a complete wrong number
*/
export function isCompleteWrongNumber(
input: string,
targetNumbers: number[],
minLengthForWrong: number = 2
): boolean {
const number = parseInt(input)
if (isNaN(number)) return false
const isNotTarget = !targetNumbers.includes(number)
const cannotBePrefix = !couldBePrefix(input, targetNumbers)
// It's a complete wrong number if:
// 1. It's not a target AND it cannot be a prefix of any target, OR
// 2. It's not a target AND it's long enough AND can be a prefix (but since it's not exact, it's wrong)
return isNotTarget && cannotBePrefix
}
/**
* Determine if input should trigger an incorrect guess
*/
export function shouldTriggerIncorrectGuess(
input: string,
targetNumbers: number[],
foundNumbers: number[],
hasGuessesRemaining: boolean = true
): boolean {
if (!hasGuessesRemaining) return false
const number = parseInt(input)
if (isNaN(number)) return false
// Don't trigger if it's a correct answer (even if already found)
if (targetNumbers.includes(number)) return false
const couldBeValidPrefix = couldBePrefix(input, targetNumbers)
// Trigger if it's clearly wrong (length >= 2 or can't be any prefix)
return input.length >= 2 || !couldBeValidPrefix
}
/**
* Check if a number is correct and available to be guessed
*/
export function isCorrectAndAvailable(
number: number,
targetNumbers: number[],
foundNumbers: number[]
): boolean {
return targetNumbers.includes(number) && !foundNumbers.includes(number)
}

View File

@@ -0,0 +1 @@
import '@testing-library/jest-dom'

View File

@@ -8,46 +8,6 @@
background: var(--colors-gray-50)
}
.sticky_top {
sticky: top
}
.align_center {
align: center
}
.pos_sticky {
position: sticky
}
.top_0 {
top: var(--spacing-0)
}
.z_10 {
z-index: 10
}
.text_brand\.800 {
color: var(--colors-brand-800)
}
.text-decor_none {
text-decoration: none
}
.text_brand\.600 {
color: var(--colors-brand-600)
}
.justify_space-between {
justify-content: space-between
}
.gap_10px {
gap: 10px
}
.fs_3xl {
font-size: var(--font-sizes-3xl)
}
@@ -338,20 +298,13 @@
box-shadow: var(--shadows-modal)
}
.hover\:bg_brand\.50:is(:hover, [data-hover]) {
background: var(--colors-brand-50)
}
.hover\:bg_red\.700:is(:hover, [data-hover]) {
background: var(--colors-red-700)
}
@media screen and (min-width: 64em) {
.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(3, minmax(0, 1fr))
}
.lg\:grid-cols_repeat\(2\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(2, minmax(0, 1fr))
@media screen and (min-width: 64em) {
.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(3, minmax(0, 1fr))
}
}
}

View File

@@ -1,5 +1,17 @@
@layer utilities {
.h_800pt {
height: 800pt
}
.w_200pt {
width: 200pt
}
.h_250pt {
height: 250pt
}
.max-w_600px {
max-width: 600px
}
@@ -132,12 +144,12 @@
text-shadow: 2px 2px 4px rgba(0,0,0,0.1)
}
.w_min\(80vw\,_600px\) {
width: min(80vw, 600px)
.w_min\(90vw\,_800px\) {
width: min(90vw, 800px)
}
.h_min\(40vh\,_350px\) {
height: min(40vh, 350px)
.h_min\(70vh\,_600px\) {
height: min(70vh, 600px)
}
.transition_transform_0\.3s_ease {
@@ -163,20 +175,28 @@
padding: 16px
}
.max-h_60vh {
max-height: 60vh
}
.overflow-y_auto {
overflow-y: auto
}
.d_grid {
display: grid
}
.grid-cols_repeat\(auto-fit\,_minmax\(120px\,_1fr\)\) {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr))
.w_fit-content {
width: fit-content
}
.perspective_1000px {
perspective: 1000px
}
.h_140px {
height: 140px
.max-w_200px {
max-width: 200px
}
.transform_rotateY\(0deg\) {
@@ -228,6 +248,34 @@
width: 100%
}
.mt_12px {
margin-top: 12px
}
.p_8px_12px {
padding: 8px 12px
}
.bg_blue\.50 {
background: var(--colors-blue-50)
}
.border_blue\.200 {
border-color: var(--colors-blue-200)
}
.text_blue\.700 {
color: var(--colors-blue-700)
}
.ml_8px {
margin-left: 8px
}
.font_normal {
font-weight: var(--font-weights-normal)
}
.mb_20px {
margin-bottom: 20px
}
@@ -378,6 +426,10 @@
animation: blink 1s infinite
}
.min-h_0 {
min-height: var(--sizes-0)
}
.pos_fixed {
position: fixed
}
@@ -730,20 +782,20 @@
overflow: auto
}
.w_280pt {
width: 280pt
.w_600pt {
width: 600pt
}
.h_360pt {
height: 360pt
.h_500pt {
height: 500pt
}
.w_100pt {
width: 100pt
.w_120pt {
width: 120pt
}
.h_130pt {
height: 130pt
.h_160pt {
height: 160pt
}
.hover\:transform_translateY\(-1px\):is(:hover, [data-hover]) {
@@ -777,10 +829,94 @@
.hover\:text_gray\.800:is(:hover, [data-hover]) {
color: var(--colors-gray-800)
}
@media (max-width: 768px) {
.\[\@media_\(max-width\:_768px\)\]\:h_130px {
height: 130px
}
.\[\@media_\(max-width\:_768px\)\]\:min-w_100px {
min-width: 100px
}
}
@media (max-width: 480px) {
.\[\@media_\(max-width\:_480px\)\]\:h_120px {
height: 120px
}
.\[\@media_\(max-width\:_480px\)\]\:min-w_90px {
min-width: 90px
}
}
@media screen and (min-width: 48em) {
.md\:px_4 {
padding-inline: var(--spacing-4)
}
}
@media (max-width: 768px) {
.\[\@media_\(max-width\:_768px\)\]\:gap_10px {
gap: 10px
}
.\[\@media_\(max-width\:_768px\)\]\:h_130px {
height: 130px
}
.\[\@media_\(max-width\:_768px\)\]\:min-w_100px {
min-width: 100px
}
.\[\@media_\(max-width\:_768px\)\]\:fs_40px {
font-size: 40px
}
.\[\@media_\(max-width\:_768px\)\]\:gap_10px {
gap: 10px
}
.\[\@media_\(max-width\:_768px\)\]\:h_130px {
height: 130px
}
.\[\@media_\(max-width\:_768px\)\]\:min-w_100px {
min-width: 100px
}
.\[\@media_\(max-width\:_768px\)\]\:fs_40px {
font-size: 40px
}
}
@media (max-width: 480px) {
.\[\@media_\(max-width\:_480px\)\]\:gap_8px {
gap: 8px
}
.\[\@media_\(max-width\:_480px\)\]\:h_120px {
height: 120px
}
.\[\@media_\(max-width\:_480px\)\]\:min-w_90px {
min-width: 90px
}
.\[\@media_\(max-width\:_480px\)\]\:fs_32px {
font-size: 32px
}
.\[\@media_\(max-width\:_480px\)\]\:gap_8px {
gap: 8px
}
.\[\@media_\(max-width\:_480px\)\]\:h_120px {
height: 120px
}
.\[\@media_\(max-width\:_480px\)\]\:min-w_90px {
min-width: 90px
}
.\[\@media_\(max-width\:_480px\)\]\:fs_32px {
font-size: 32px
}
}
}

View File

@@ -362,7 +362,6 @@
.md\:grid-cols_repeat\(2\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(2, minmax(0, 1fr))
}
.md\:px_6 {
padding-inline: var(--spacing-6)
}

View File

@@ -4,82 +4,6 @@
min-height: 100vh
}
.space_y-2 {
space: y-2
}
.pos_sticky {
position: sticky
}
.top_0 {
top: var(--spacing-0)
}
.z_10 {
z-index: 10
}
.text_brand\.800 {
color: var(--colors-brand-800)
}
.py_2 {
padding-block: var(--spacing-2)
}
.justify_space-between {
justify-content: space-between
}
.gap_10px {
gap: 10px
}
.gap_3 {
gap: var(--spacing-3)
}
.w_120 {
width: 120px
}
.h_336 {
height: 336px
}
.w_180 {
width: 180px
}
.h_240 {
height: 240px
}
.color-scheme_place-value {
color-scheme: place-value
}
.w_80 {
width: var(--sizes-80)
}
.h_120 {
height: 120px
}
.w_full {
width: var(--sizes-full)
}
.h_full {
height: var(--sizes-full)
}
.\[\&_svg\]\:object_contain svg {
object-fit: contain
}
.bg_linear-gradient\(135deg\,_\#667eea_0\%\,_\#764ba2_100\%\) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)
}
@@ -703,7 +627,6 @@
.md\:grid-cols_repeat\(2\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(2, minmax(0, 1fr))
}
.md\:p_8 {
padding: var(--spacing-8)
}
@@ -711,7 +634,6 @@
.md\:fs_3xl {
font-size: var(--font-sizes-3xl)
}
.md\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(3, minmax(0, 1fr))
}

View File

@@ -8,46 +8,6 @@
background: gradient-to-br from-brand.50 to-brand.100
}
.align_center {
align: center
}
.py_6 {
padding-block: var(--spacing-6)
}
.fs_2xl {
font-size: var(--font-sizes-2xl)
}
.text_brand\.800 {
color: var(--colors-brand-800)
}
.py_2 {
padding-block: var(--spacing-2)
}
.rounded_lg {
border-radius: var(--radii-lg)
}
.font_medium {
font-weight: var(--font-weights-medium)
}
.max-w_7xl {
max-width: var(--sizes-7xl)
}
.justify_space-between {
justify-content: space-between
}
.gap_10px {
gap: 10px
}
.leading_tight {
line-height: var(--line-heights-tight)
}
@@ -238,14 +198,6 @@
margin-top: var(--spacing-8)
}
.hover\:bg_brand\.50:is(:hover, [data-hover]) {
background: var(--colors-brand-50)
}
.hover\:transform_translateY\(-1px\):is(:hover, [data-hover]) {
transform: translateY(-1px)
}
.hover\:bg_brand\.700:is(:hover, [data-hover]) {
background: var(--colors-brand-700)
}
@@ -278,7 +230,6 @@
.md\:grid-cols_3 {
grid-template-columns: repeat(3, minmax(0, 1fr))
}
.md\:fs_6xl {
font-size: var(--font-sizes-6xl)
}
@@ -290,15 +241,5 @@
.md\:grid-cols_3 {
grid-template-columns: repeat(3, minmax(0, 1fr))
}
.md\:px_6 {
padding-inline: var(--spacing-6)
}
}
@media screen and (min-width: 64em) {
.lg\:px_8 {
padding-inline: var(--spacing-8)
}
}
}

View File

@@ -12,18 +12,6 @@
border-radius: var(--radii-lg)
}
.shadow_modal {
box-shadow: var(--shadows-modal)
}
.z_9999 {
z-index: 9999
}
.animation_fadeIn_0\.2s_ease-out {
animation: fadeIn 0.2s ease-out
}
.transform_rotate\(180deg\) {
transform: rotate(180deg)
}

View File

@@ -1,9 +1,5 @@
@layer utilities {
.opacity_0\.9 {
opacity: 0.9
}
.pos_fixed {
position: fixed
}
@@ -20,14 +16,8 @@
z-index: 40
}
.opacity_0\.8 {
opacity: 0.8
}
.transition_opacity {
transition-property: var(--transition-prop, opacity);
transition-timing-function: var(--transition-easing, cubic-bezier(0.4, 0, 0.2, 1));
transition-duration: var(--transition-duration, 150ms)
.opacity_0\.9 {
opacity: 0.9
}
.gap_2 {
@@ -218,18 +208,6 @@
padding-block: var(--spacing-3)
}
.hover\:text_brand\.700:is(:hover, [data-hover]) {
color: var(--colors-brand-700)
}
.hover\:text_gray\.700:is(:hover, [data-hover]) {
color: var(--colors-gray-700)
}
.hover\:transform_scale\(1\.1\):is(:hover, [data-hover]) {
transform: scale(1.1)
}
.hover\:opacity_1:is(:hover, [data-hover]) {
opacity: 1
}
@@ -253,4 +231,16 @@
.hover\:bg_gray\.50:is(:hover, [data-hover]) {
background: var(--colors-gray-50)
}
.hover\:text_brand\.700:is(:hover, [data-hover]) {
color: var(--colors-brand-700)
}
.hover\:text_gray\.700:is(:hover, [data-hover]) {
color: var(--colors-gray-700)
}
.hover\:transform_scale\(1\.1\):is(:hover, [data-hover]) {
transform: scale(1.1)
}
}

View File

@@ -8,10 +8,6 @@
font-weight: var(--font-weights-bold)
}
.align_start {
align: start
}
.bg_gray\.100 {
background: var(--colors-gray-100)
}

View File

@@ -20,10 +20,6 @@
overflow: hidden
}
.align_center {
align: center
}
.top_-20px {
top: -20px
}

View File

@@ -8,10 +8,6 @@
font-weight: var(--font-weights-bold)
}
.align_center {
align: center
}
.pos_relative {
position: relative
}

View File

@@ -4,138 +4,6 @@
font-size: var(--font-sizes-xl)
}
.align_center {
align: center
}
.animation_spin_1s_linear_infinite {
animation: spin 1s linear infinite
}
.text_brand\.600 {
color: var(--colors-brand-600)
}
.mb_3 {
margin-bottom: var(--spacing-3)
}
.text_gray\.400 {
color: var(--colors-gray-400)
}
.fs_3xl {
font-size: var(--font-sizes-3xl)
}
.bg_gray\.100 {
background: var(--colors-gray-100)
}
.animation_pulse {
animation: var(--animations-pulse)
}
.p_6 {
padding: var(--spacing-6)
}
.bg_amber\.50 {
background: var(--colors-amber-50)
}
.border_amber\.200 {
border-color: var(--colors-amber-200)
}
.text_center {
text-align: center
}
.fs_2xl {
font-size: var(--font-sizes-2xl)
}
.text_amber\.800 {
color: var(--colors-amber-800)
}
.text_amber\.700 {
color: var(--colors-amber-700)
}
.mt_1 {
margin-top: var(--spacing-1)
}
.gap_2 {
gap: var(--spacing-2)
}
.grid-cols_repeat\(2\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(2, minmax(0, 1fr))
}
.max-w_full {
max-width: var(--sizes-full)
}
.max-h_full {
max-height: var(--sizes-full)
}
.\[\&_svg\]\:max-w_100\% svg {
max-width: 100%
}
.\[\&_svg\]\:max-h_100\% svg {
max-height: 100%
}
.\[\&_svg\]\:w_auto svg {
width: auto
}
.\[\&_svg\]\:h_auto svg {
height: auto
}
.w_160pt {
width: 160pt
}
.w_200pt {
width: 200pt
}
.h_280pt {
height: 280pt
}
.min-h_200px,.\[\&_svg\]\:min-h_200px svg {
min-height: 200px
}
.w_240pt {
width: 240pt
}
.h_320pt {
height: 320pt
}
.transform_scale\(2\.5\) {
transform: scale(2.5)
}
.\[\&_svg\]\:object_contain svg {
object-fit: contain
}
.h_200pt {
height: 200pt
}
.px_3 {
padding-inline: var(--spacing-3)
}

View File

@@ -4,69 +4,21 @@
background: green.25
}
.w_8 {
width: var(--sizes-8)
}
.\[\&_svg\]\:w_auto svg {
width: auto
}
.h_8 {
height: var(--sizes-8)
}
.\[\&_svg\]\:h_auto svg {
height: auto
}
.border_gray\.300 {
border-color: var(--colors-gray-300)
}
.\[\&_svg\]\:w_100\%\! svg {
width: 100% !important
}
.border-t_brand\.600 {
border-top-color: var(--colors-brand-600)
}
.flex_column {
flex-direction: column
}
.bg_red\.50 {
background: var(--colors-red-50)
}
.border_1px_solid {
border: 1px solid
}
.border_red\.200 {
border-color: var(--colors-red-200)
}
.p_4 {
padding: var(--spacing-4)
}
.fs_2xl {
font-size: var(--font-sizes-2xl)
}
.mb_2 {
margin-bottom: var(--spacing-2)
}
.text_red\.700 {
color: var(--colors-red-700)
}
.text_center {
text-align: center
}
.text_red\.600 {
color: var(--colors-red-600)
}
.fs_xs {
font-size: var(--font-sizes-xs)
}
.mt_1 {
margin-top: var(--spacing-1)
}
.\[\&_svg\]\:h_100\%\! svg {
height: 100% !important
}
.cursor_pointer {
cursor: pointer
@@ -165,18 +117,18 @@
.justify_center {
justify-content: center
}
.\[\&_svg\]\:w_100\% svg {
width: 100%
}
.\[\&_svg\]\:h_100\% svg {
height: 100%
}
.\[\&_svg\]\:max-w_100\% svg {
max-width: 100%
}
.\[\&_svg\]\:max-h_100\% svg {
max-height: 100%
}
.\[\&_svg\]\:w_auto svg {
width: auto
}
.\[\&_svg\]\:h_auto svg {
height: auto
}
.hover\:bg_green\.50:is(:hover, [data-hover]) {
background: var(--colors-green-50)

View File

@@ -9,14 +9,14 @@
@import './tokens/keyframes.css';
@layer utilities {
.sticky_top {
sticky: top
}
.self_start {
align-self: start
}
.h_800pt {
height: 800pt
}
.max-w_600px {
max-width: 600px
}
@@ -133,32 +133,40 @@
text-shadow: 2px 2px 4px rgba(0,0,0,0.1)
}
.w_min\(80vw\,_600px\) {
width: min(80vw, 600px)
.w_min\(90vw\,_800px\) {
width: min(90vw, 800px)
}
.h_min\(40vh\,_350px\) {
height: min(40vh, 350px)
.h_min\(70vh\,_600px\) {
height: min(70vh, 600px)
}
.transition_transform_0\.3s_ease {
transition: transform 0.3s ease
}
.\[\&_svg\]\:object_contain svg {
object-fit: contain
}
.p_16px {
padding: 16px
}
.grid-cols_repeat\(auto-fit\,_minmax\(120px\,_1fr\)\) {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr))
.max-h_60vh {
max-height: 60vh
}
.w_fit-content {
width: fit-content
}
.perspective_1000px {
perspective: 1000px
}
.h_140px {
height: 140px
.max-w_200px {
max-width: 200px
}
.transform_rotateY\(0deg\) {
@@ -185,6 +193,10 @@
border: 3px solid #5f3dc4
}
.opacity_0\.8 {
opacity: 0.8
}
.backface_hidden {
backface-visibility: hidden;
-webkit-backface-visibility: hidden
@@ -202,6 +214,22 @@
transform: rotateY(180deg)
}
.mt_12px {
margin-top: 12px
}
.p_8px_12px {
padding: 8px 12px
}
.ml_8px {
margin-left: 8px
}
.font_normal {
font-weight: var(--font-weights-normal)
}
.mb_20px {
margin-bottom: 20px
}
@@ -343,6 +371,10 @@
animation: blink 1s infinite
}
.min-h_0 {
min-height: var(--sizes-0)
}
.left_0 {
left: var(--spacing-0)
}
@@ -547,16 +579,12 @@
overflow: auto
}
.w_280pt {
width: 280pt
.w_600pt {
width: 600pt
}
.h_360pt {
height: 360pt
}
.h_130pt {
height: 130pt
.h_500pt {
height: 500pt
}
.from_blue\.50 {
@@ -643,38 +671,6 @@
background: var(--colors-indigo-100)
}
.space_y-2 {
space: y-2
}
.w_120 {
width: 120px
}
.h_336 {
height: 336px
}
.w_180 {
width: 180px
}
.h_240 {
height: 240px
}
.color-scheme_place-value {
color-scheme: place-value
}
.w_80 {
width: var(--sizes-80)
}
.h_120 {
height: 120px
}
.bg_linear-gradient\(135deg\,_\#667eea_0\%\,_\#764ba2_100\%\) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)
}
@@ -791,6 +787,10 @@
color: var(--colors-red-800)
}
.transform_scale\(2\.5\) {
transform: scale(2.5)
}
.mb_6 {
margin-bottom: var(--spacing-6)
}
@@ -851,6 +851,10 @@
color: var(--colors-purple-600)
}
.h_200pt {
height: 200pt
}
.w_180pt {
width: 180pt
}
@@ -879,10 +883,6 @@
background: gradient-to-br from-brand.50 to-brand.100
}
.py_6 {
padding-block: var(--spacing-6)
}
.max-w_2xl {
max-width: var(--sizes-2xl)
}
@@ -955,6 +955,10 @@
color: var(--colors-blue-900)
}
.w_200pt {
width: 200pt
}
.h_250pt {
height: 250pt
}
@@ -975,14 +979,6 @@
gap: var(--spacing-8)
}
.z_9999 {
z-index: 9999
}
.animation_fadeIn_0\.2s_ease-out {
animation: fadeIn 0.2s ease-out
}
.transform_rotate\(180deg\) {
transform: rotate(180deg)
}
@@ -995,6 +991,10 @@
box-shadow: var(--shadows-lg)
}
.p_6 {
padding: var(--spacing-6)
}
.max-w_400px {
max-width: 400px
}
@@ -1015,10 +1015,6 @@
height: var(--sizes-1\.5)
}
.opacity_0\.9 {
opacity: 0.9
}
.pos_fixed {
position: fixed
}
@@ -1027,14 +1023,8 @@
z-index: 40
}
.opacity_0\.8 {
opacity: 0.8
}
.transition_opacity {
transition-property: var(--transition-prop, opacity);
transition-timing-function: var(--transition-easing, cubic-bezier(0.4, 0, 0.2, 1));
transition-duration: var(--transition-duration, 150ms)
.opacity_0\.9 {
opacity: 0.9
}
.transition_transform {
@@ -1083,10 +1073,6 @@
margin-inline: auto
}
.align_start {
align: start
}
.pt_6 {
padding-top: var(--spacing-6)
}
@@ -1127,6 +1113,10 @@
border-color: var(--colors-brand-600)
}
.grid-cols_repeat\(2\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(2, minmax(0, 1fr))
}
.bg_gradient-to-r_from-green\.50_to-emerald\.50 {
background: gradient-to-r from-green.50 to-emerald.50
}
@@ -1171,6 +1161,10 @@
flex-wrap: wrap
}
.bg_gray\.100 {
background: var(--colors-gray-100)
}
.text_gray\.700 {
color: var(--colors-gray-700)
}
@@ -1344,6 +1338,10 @@
background: var(--colors-green-50)
}
.text_brand\.600 {
color: var(--colors-brand-600)
}
.w_3 {
width: var(--sizes-3)
}
@@ -1352,6 +1350,10 @@
height: var(--sizes-3)
}
.animation_pulse {
animation: var(--animations-pulse)
}
.w_2 {
width: var(--sizes-2)
}
@@ -1388,90 +1390,6 @@
font-size: var(--font-sizes-xl)
}
.align_center {
align: center
}
.text_brand\.600 {
color: var(--colors-brand-600)
}
.bg_gray\.100 {
background: var(--colors-gray-100)
}
.animation_pulse {
animation: var(--animations-pulse)
}
.p_6 {
padding: var(--spacing-6)
}
.bg_amber\.50 {
background: var(--colors-amber-50)
}
.border_amber\.200 {
border-color: var(--colors-amber-200)
}
.text_amber\.800 {
color: var(--colors-amber-800)
}
.text_amber\.700 {
color: var(--colors-amber-700)
}
.grid-cols_repeat\(2\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(2, minmax(0, 1fr))
}
.max-w_full {
max-width: var(--sizes-full)
}
.max-h_full {
max-height: var(--sizes-full)
}
.w_160pt {
width: 160pt
}
.w_200pt {
width: 200pt
}
.h_280pt {
height: 280pt
}
.min-h_200px,.\[\&_svg\]\:min-h_200px svg {
min-height: 200px
}
.w_240pt {
width: 240pt
}
.h_320pt {
height: 320pt
}
.transform_scale\(2\.5\) {
transform: scale(2.5)
}
.\[\&_svg\]\:object_contain svg {
object-fit: contain
}
.h_200pt {
height: 200pt
}
.py_1 {
padding-block: var(--spacing-1)
}
@@ -1548,14 +1466,6 @@
max-height: 100%
}
.\[\&_svg\]\:w_100\% svg {
width: 100%
}
.\[\&_svg\]\:h_100\% svg {
height: 100%
}
.\[\&_svg\]\:d_block svg {
display: block
}
@@ -1652,6 +1562,10 @@
height: var(--sizes-5)
}
.border_gray\.300 {
border-color: var(--colors-gray-300)
}
.bg_white {
background: var(--colors-white)
}
@@ -1748,13 +1662,13 @@
background: green.25
}
.border_gray\.300 {
border-color: var(--colors-gray-300)
}
.\[\&_svg\]\:w_100\%\! svg {
width: 100% !important
}
.border-t_brand\.600 {
border-top-color: var(--colors-brand-600)
}
.\[\&_svg\]\:h_100\%\! svg {
height: 100% !important
}
.fs_4xl {
font-size: var(--font-sizes-4xl)
@@ -1794,6 +1708,14 @@
opacity: 0.6
}
.\[\&_svg\]\:w_100\% svg {
width: 100%
}
.\[\&_svg\]\:h_100\% svg {
height: 100%
}
.mb_2 {
margin-bottom: var(--spacing-2)
}
@@ -2042,14 +1964,6 @@
border-color: var(--colors-gray-300)
}
.hover\:text_brand\.700:is(:hover, [data-hover]) {
color: var(--colors-brand-700)
}
.hover\:text_gray\.700:is(:hover, [data-hover]) {
color: var(--colors-gray-700)
}
.hover\:opacity_1:is(:hover, [data-hover]) {
opacity: 1
}
@@ -2070,6 +1984,14 @@
background: var(--colors-gray-50)
}
.hover\:text_brand\.700:is(:hover, [data-hover]) {
color: var(--colors-brand-700)
}
.hover\:text_gray\.700:is(:hover, [data-hover]) {
color: var(--colors-gray-700)
}
.hover\:text_gray\.900:is(:hover, [data-hover]) {
color: var(--colors-gray-900)
}
@@ -2159,6 +2081,10 @@
padding-inline: var(--spacing-4)
}
.md\:px_6 {
padding-inline: var(--spacing-6)
}
.md\:fs_5xl {
font-size: var(--font-sizes-5xl)
}
@@ -2171,6 +2097,10 @@
font-size: var(--font-sizes-3xl)
}
.md\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(3, minmax(0, 1fr))
}
.md\:fs_6xl {
font-size: var(--font-sizes-6xl)
}
@@ -2179,15 +2109,7 @@
font-size: var(--font-sizes-xl)
}
.md\:grid-cols_3 {
grid-template-columns: repeat(3, minmax(0, 1fr))
}
.md\:px_6 {
padding-inline: var(--spacing-6)
}
.md\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\) {
.md\:grid-cols_3,.md\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(3, minmax(0, 1fr))
}
@@ -2197,18 +2119,6 @@
}
@media screen and (min-width: 64em) {
.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\),.lg\:grid-cols_repeat\(3\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(3, minmax(0, 1fr))
}
.lg\:grid-cols_repeat\(2\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(2, minmax(0, 1fr))
}
.lg\:px_8 {
padding-inline: var(--spacing-8)
}
.lg\:grid-cols_repeat\(5\,_minmax\(0\,_1fr\)\) {
grid-template-columns: repeat(5, minmax(0, 1fr))
}
@@ -2217,4 +2127,42 @@
grid-template-columns: repeat(3, minmax(0, 1fr))
}
}
@media (max-width: 768px) {
.\[\@media_\(max-width\:_768px\)\]\:gap_10px {
gap: 10px
}
.\[\@media_\(max-width\:_768px\)\]\:h_130px {
height: 130px
}
.\[\@media_\(max-width\:_768px\)\]\:min-w_100px {
min-width: 100px
}
.\[\@media_\(max-width\:_768px\)\]\:fs_40px {
font-size: 40px
}
}
@media (max-width: 480px) {
.\[\@media_\(max-width\:_480px\)\]\:gap_8px {
gap: 8px
}
.\[\@media_\(max-width\:_480px\)\]\:h_120px {
height: 120px
}
.\[\@media_\(max-width\:_480px\)\]\:min-w_90px {
min-width: 90px
}
.\[\@media_\(max-width\:_480px\)\]\:fs_32px {
font-size: 32px
}
}
}

16
apps/web/vitest.config.ts Normal file
View File

@@ -0,0 +1,16 @@
/// <reference types="vitest" />
import { defineConfig } from 'vitest/config'
import path from 'path'
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})

View File

@@ -3,14 +3,14 @@
> tsup src/index.ts --format cjs,esm --dts
CLI Building entry: src/index.ts
CLI Using tsconfig: ../../tsconfig.json
CLI tsup v7.0.0
CLI Target: node16
CLI Target: es2022
CJS Build start
ESM Build start
ESM dist/index.mjs 4.74 KB
ESM ⚡️ Build success in 101ms
CJS dist/index.js 6.51 KB
CJS ⚡️ Build success in 101ms
CJS dist/index.js 8.90 KB
CJS ⚡️ Build success in 11ms
ESM dist/index.mjs 7.03 KB
ESM ⚡️ Build success in 11ms
DTS Build start
DTS ⚡️ Build success in 347ms
DTS dist/index.d.ts 1.75 KB
ELIFECYCLE Command failed.

View File

@@ -6,12 +6,12 @@ case `uname` in
esac
if [ -z "$NODE_PATH" ]; then
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules/vitest/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules/vitest/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
else
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules/vitest/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules/vitest/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
fi
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../../../../../../node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules/vitest/vitest.mjs" "$@"
exec "$basedir/node" "$basedir/../../../../../../node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules/vitest/vitest.mjs" "$@"
else
exec node "$basedir/../../../../../../node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules/vitest/vitest.mjs" "$@"
exec node "$basedir/../../../../../../node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules/vitest/vitest.mjs" "$@"
fi

View File

@@ -1 +1 @@
../../../../../node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules/vitest
../../../../../node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules/vitest

View File

@@ -1,4 +1,3 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;

View File

@@ -6,12 +6,12 @@ case `uname` in
esac
if [ -z "$NODE_PATH" ]; then
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules/vitest/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules/vitest/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
else
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules/vitest/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules/vitest/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
fi
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../../../../../../node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules/vitest/vitest.mjs" "$@"
exec "$basedir/node" "$basedir/../../../../../../node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules/vitest/vitest.mjs" "$@"
else
exec node "$basedir/../../../../../../node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules/vitest/vitest.mjs" "$@"
exec node "$basedir/../../../../../../node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules/vitest/vitest.mjs" "$@"
fi

View File

@@ -1 +1 @@
../../../../../node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0/node_modules/vitest
../../../../../node_modules/.pnpm/vitest@1.0.0_@types+node@20.0.0_jsdom@27.0.0/node_modules/vitest

699
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff