import { describe, it, expect } from 'vitest' import { analyzeProse } from './prose' // These tests pin the rules-based prose checker's behavior. For an ESL writer a // *wrong* nudge costs trust faster than a missed one earns it, so every rule is // tested in two directions: a true-positive case that must fire, and at least // one near-miss "guard" case that must stay silent. Sentences are padded past // the 8-word floor that analyzeProse needs before it offers advice. const rulesFor = (text: string) => analyzeProse(text).map((h) => h.rule) // Assert a given rule appears among the hints for this text. function expectRule(text: string, rule: string) { expect(rulesFor(text), `expected "${rule}" for: ${text}`).toContain(rule) } // Assert the text produces no hints at all (false-positive guard). function expectClean(text: string) { expect(analyzeProse(text), `expected no hints for: ${text}`).toEqual([]) } describe('analyzeProse — floor', () => { it('stays quiet on very short drafts (under 8 English words)', () => { expect(analyzeProse('he go now')).toEqual([]) }) it('returns hints highest-priority first', () => { // This 36-word run-on also contains a lowercase "i"; run-on must lead. const hints = analyzeProse( 'yesterday i walked all the way to the market and i bought apples and oranges and i saw my friend there and we talked and laughed and walked back home together in the warm afternoon sun.', ) expect(hints[0].rule).toBe('runon') expect(hints.map((h) => h.rule)).toContain('cap-i') }) }) describe('run-ons', () => { it('flags a long, comma-heavy sentence', () => { expectRule( 'I went to the market and I bought some apples and then I saw my friend and we talked for a while and after that we went to the cafe and ordered coffee and cake together.', 'runon', ) }) it('leaves a normal sentence alone', () => { expectClean('The garden was quiet in the morning and the rain fell softly on the leaves.') }) }) describe('comma splices', () => { it('flags two independent clauses joined by a comma', () => { expectRule('I finished the report, I sent it to my boss right away today.', 'splice') }) it('catches a splice with a verb not on any list', () => { expectRule('The garden was quiet in the morning, she poured a cup of tea slowly.', 'splice') }) it('does not flag an introductory transition (However, …)', () => { expectClean('However, we decided to stay home and rest for the entire weekend.') }) it('does not flag an introductory phrase (After dinner, …)', () => { expectClean('After dinner, I went home and read a book until I fell asleep.') }) it('does not flag a pronoun list after a comma', () => { expectClean('We invited my brother, you and your sister to the dinner party.') }) }) describe('unclear antecedents', () => { it('flags a vague "This + verb" opener after a prior sentence', () => { expectRule( 'We changed the whole schedule last week. This makes everyone confused about the new times.', 'antecedent', ) }) it('leaves "This" alone when it names its noun', () => { expectClean( 'We changed the whole schedule last week. This new schedule confuses everyone about the times.', ) }) }) describe('oxford comma', () => { it('flags a 3-item noun list missing the serial comma', () => { expectRule('My mother, my father and my sister came to visit us last summer.', 'oxford') }) it('flags a verb-phrase list missing the serial comma', () => { expectRule('We cleaned the kitchen, washed the dishes and took out the trash.', 'oxford') }) it('does not flag a list that already has the serial comma', () => { expectClean('I bought apples, bananas, and oranges at the store this morning.') }) it('does not flag a compound predicate as a list', () => { expectClean('After dinner, I went home and read a book until I fell asleep.') }) }) describe('a / an', () => { it('flags "a" before a vowel sound', () => { expectRule('She found a umbrella and a old book on the table by the window.', 'article') }) it('flags "an" before a consonant sound', () => { expectRule('He gave me an book and an pencil to use during the long exam.', 'article') }) it('respects sound exceptions (a university, an hour)', () => { expectClean('He is a university student and it took an hour to arrive here.') }) }) describe('uncountable plurals', () => { it('flags a pluralized mass noun', () => { expectRule('She gave me many informations about the new job opening today.', 'uncountable') }) it('leaves the singular mass noun alone', () => { expectClean('He shared some useful advice and helped me with my homework tonight.') }) }) describe('capitalization of proper nouns', () => { it('flags lowercase languages and days', () => { expectRule('I am learning english and french during my free time every week.', 'propercap') expectRule('We will meet on monday to discuss the final project together soon.', 'propercap') }) it('does not flag ambiguous homographs (may, march)', () => { expectClean('They may visit in the spring and march through the old town square.') }) it('leaves already-capitalized proper nouns alone', () => { expectClean('I will study English and Chinese together this coming summer break here.') }) }) describe('subject–verb agreement', () => { it('flags a bare verb after he/she/it', () => { expectRule('My brother is busy but he go to the gym every single morning.', 'sva') expectRule('She have a lovely garden full of roses behind her small house.', 'sva') }) it('flags a sentence-initial subject too', () => { expectRule('It make me happy whenever the sun comes out after the rain.', 'sva') }) it('does not flag causative/perception frames (let it go, make it work)', () => { expectClean('Please let it go and try to enjoy the rest of your day.') expectClean('We should make it work before the big meeting tomorrow afternoon.') }) }) describe('plural after a number', () => { it('flags a singular noun after a cardinal number', () => { expectRule('I bought three apple and some bread at the market this morning.', 'plural') }) it('does not flag an adjective between number and noun', () => { expectClean('We adopted two small dogs from the shelter near our apartment today.') }) it('does not flag irregular plurals (five people)', () => { expectClean('There were five people waiting patiently outside in the cold morning rain.') }) it('does not flag an already-plural noun', () => { expectClean('She has two cats and they sleep all day long on the couch.') }) }) describe('double determiner', () => { it('flags a stacked article + possessive', () => { expectRule('Please put the my book back on the shelf when you finish it.', 'doubledet') }) }) describe('there is + plural', () => { it('flags "there is" with a plural quantifier', () => { expectRule('There is many reasons to visit the coast during the warm months.', 'thereis') }) it('does not flag "there is some" with a mass noun', () => { expectClean('There is some water left in the bottle if you are thirsty now.') }) }) describe('its / it’s', () => { it("flags it's used as a possessive", () => { expectRule("The cat licked it's own paw while sitting quietly by the window.", 'its') }) it('flags its used where "it is" is meant', () => { expectRule('I think its a wonderful idea to travel together next year sometime.', 'its') }) it('leaves correct possessive "its" alone', () => { expectClean('The dog wagged its tail happily when we came home that evening.') }) }) describe('than / then', () => { it('flags a comparative followed by "then"', () => { expectRule('This cake tastes much better then the one we made last weekend.', 'than') }) it('leaves sequential "then" alone', () => { expectClean('We ate dinner and then we watched a long movie on the sofa.') }) }) describe('comma after a transition', () => { it('flags a sentence-initial transition with no comma', () => { expectRule('However we decided to stay home and rest for the whole weekend.', 'introcomma') }) it('does not flag a sequence word (Then …)', () => { expectClean('Then we walked to the park and fed the ducks by the pond.') }) }) describe('spacing around punctuation', () => { it('flags a missing space after a comma', () => { expectRule('I like apples,bananas and grapes from the little shop down the street.', 'space-after') }) it('flags a space before punctuation', () => { expectRule('She smiled warmly , then walked away without saying a single word today.', 'space-punct') }) it('does not flag thousands separators in numbers', () => { expectClean('The list had 1,000 items and cost us about 2,500 dollars in total.') }) }) describe('capitalization basics', () => { it('flags a lowercase standalone "i"', () => { expectRule('Yesterday i walked to the store and bought milk, eggs, and bread.', 'cap-i') }) it('flags a sentence that does not start with a capital', () => { expectRule('The sun set slowly. it was a beautiful evening down by the river.', 'cap-sentence') }) })