the next generation of the in-browser educational proof assistant
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #3 from joshcbrown/push-oquznqsqnkqv

Add testing framework

authored by

Liam O'Connor and committed by
GitHub
969b9e47 e1bd4951

+101 -13
+24
.github/workflows/node.js.yml
··· 1 + name: Node.js CI 2 + 3 + on: [push, pull_request] 4 + 5 + jobs: 6 + build: 7 + runs-on: ubuntu-latest 8 + 9 + strategy: 10 + matrix: 11 + node-version: [22.x, 24.x] 12 + 13 + steps: 14 + - uses: actions/checkout@v4 15 + - name: Use Node.js ${{ matrix.node-version }} 16 + uses: actions/setup-node@v4 17 + with: 18 + node-version: ${{ matrix.node-version }} 19 + cache: "npm" 20 + - run: npm install 21 + - run: npm ci 22 + - run: npm run res:build 23 + - run: npm run build --if-present 24 + - run: npm test
+1
.gitignore
··· 27 27 .bsb.lock 28 28 **/*.res.mjs 29 29 src/*.mjs 30 + tests/*.mjs
+6 -1
package.json
··· 11 11 "res:build": "rescript", 12 12 "res:clean": "rescript clean", 13 13 "res:dev": "rescript -w", 14 - "res:format": "rescript format -all" 14 + "res:format": "rescript format -all", 15 + "test": "pta 'tests/*.mjs'", 16 + "test-watch": "onchange --initial '{tests,src}/*.mjs' -- pta 'tests/*.mjs'" 15 17 }, 16 18 "dependencies": { 17 19 "@rescript/react": "^0.13.1", ··· 20 22 "rescript": "^11.1.4" 21 23 }, 22 24 "devDependencies": { 25 + "@dusty-phillips/rescript-zora": "^5.0.1", 23 26 "@eslint/js": "^9.25.0", 24 27 "@rescript/core": "^1.6.1", 25 28 "@rescript/std": "^11.1.4", ··· 30 33 "eslint-plugin-react-hooks": "^5.2.0", 31 34 "eslint-plugin-react-refresh": "^0.4.19", 32 35 "globals": "^16.0.0", 36 + "onchange": "^7.1.0", 37 + "pta": "^1.3.0", 33 38 "typescript": "~5.8.3", 34 39 "typescript-eslint": "^8.30.1", 35 40 "vite": "^6.3.5"
+8 -12
rescript.json
··· 1 1 { 2 2 "name": "holbert-ng", 3 - "sources": { 4 - "dir": "src", 5 - "subdirs": true 6 - }, 3 + "sources": [ 4 + { "dir": "src", "subdirs": true }, 5 + { "dir": "tests", "subdirs": true, "type": "dev" } 6 + ], 7 7 "package-specs": { 8 8 "module": "esmodule", 9 9 "in-source": true 10 10 }, 11 11 "suffix": ".mjs", 12 - "bs-dependencies": [ 13 - "@rescript/core", 14 - "@rescript/react" 15 - ], 16 - "bsc-flags": [ 17 - "-open RescriptCore" 18 - ], 19 - "jsx": { "version": 4 }, 12 + "bs-dependencies": ["@rescript/core", "@rescript/react"], 13 + "bs-dev-dependencies": ["@dusty-phillips/rescript-zora"], 14 + "bsc-flags": ["-open RescriptCore"], 15 + "jsx": { "version": 4 } 20 16 }
+37
tests/SExpTest.res
··· 1 + open Zora 2 + open SExp 3 + 4 + module Util = TestUtil.MakeTerm(SExp) 5 + 6 + zoraBlock("parse symbol", t => { 7 + t->block("single char", t => t->Util.testParse("x", Symbol({name: "x"}))) 8 + t->block("multi char", t => t->Util.testParse("xyz", Symbol({name: "xyz"}))) 9 + }) 10 + 11 + zoraBlock("parse var", t => { 12 + t->block("single digit", t => t->Util.testParse("\\1", Var({idx: 1}))) 13 + t->block("multi digit", t => t->Util.testParse("\\234", Var({idx: 234}))) 14 + }) 15 + 16 + zoraBlock("parse schematic", t => { 17 + t->block("empty allowed", t => t->Util.testParse("?1()", Schematic({schematic: 1, allowed: []}))) 18 + t->block("one allowed", t => 19 + t->Util.testParse("?1(\\1)", Schematic({schematic: 1, allowed: [1]})) 20 + ) 21 + t->block("multiple allowed", t => 22 + t->Util.testParse("?1(\\1 \\23 \\4)", Schematic({schematic: 1, allowed: [1, 23, 4]})) 23 + ) 24 + }) 25 + 26 + zoraBlock("parse compound", t => { 27 + t->block("unit", t => t->Util.testParse("()", Compound({subexps: []}))) 28 + t->block("single", t => t->Util.testParse("(a)", Compound({subexps: [Symbol({name: "a"})]}))) 29 + t->block("multiple", t => { 30 + t->Util.testParse( 31 + "(a \\1 ?1())", 32 + Compound({ 33 + subexps: [Symbol({name: "a"}), Var({idx: 1}), Schematic({schematic: 1, allowed: []})], 34 + }), 35 + ) 36 + }) 37 + })
+25
tests/TestUtil.res
··· 1 + open Signatures 2 + open Zora 3 + 4 + let stringifyExn = (t: 'a) => JSON.stringifyAny(t, ~space=2)->Option.getExn 5 + 6 + module MakeTerm = (Term: TERM) => { 7 + let termEquivalent = (t: Zora.t, t1: Term.t, t2: Term.t, ~msg=?) => { 8 + t->ok( 9 + Term.equivalent(t1, t2), 10 + ~msg=msg->Option.getOr(`${stringifyExn(t1)} equivalent to ${stringifyExn(t2)}`), 11 + ) 12 + } 13 + let testParse = (t: Zora.t, input: string, t2: Term.t, ~msg=?) => { 14 + let res = Term.parse(input, ~scope=[], ~gen=Term.makeGen()) 15 + switch res { 16 + | Ok(res) => { 17 + t->equal(res->snd, "", ~msg=input ++ " input consumed") 18 + // NOTE: we're checking for equality here, not equivalency 19 + // error messages are better this way 20 + t->equal(res->fst, t2, ~msg?) 21 + } 22 + | Error(msg) => t->fail(~msg="parse failed: " ++ msg) 23 + } 24 + } 25 + }