Skip to content

Commit d1c150a

Browse files
test: port core.test.ts → Idris2 (partial; estate port 6/11, file 1 of 4) (#55)
## Summary Estate port **6/11** per ESTATE-ROLLOUT.adoc. **PARTIAL** — this PR ports ONLY \`tests/core.test.ts\` (1 of 4 TS test files in ubicity). The other 3 (privacy/mapper/export) await separate PRs after the unit-logic strategy review documented in repos-monorepo PR #12. | | Before | After | |---|---|---| | core.test.ts | 5 tests | **3 ported / 2 deferred** | | Pass rate | (unrun) | **3/3 PASS** | | Other TS test files (privacy/mapper/export) | 3 files, 489 LOC | Untouched — separate PRs after strategy review | | Run via | \`deno test --allow-read tests/\` | \`just test-core\` | ## What's ported (3 tests) | TS test | Idris2 approach | |---|---| | LearningExperience validation | Defines mirror records (Location/Learner/Experience/LearningExperience), constructs a literal value, asserts field presence + domain length. | | Timestamp ISO 8601 shape | Checks for \`T\` + \`Z\` markers; length sanity check. | | Domain array dedup/normalize/filter | Uses Idris2 stdlib \`nub\` + \`map toLower\` + \`filter isPrefixOf\`. | ## What's deferred (2 tests) | TS test | Why deferred | |---|---| | Core - Data persistence | Needs JSON parse + temp-file write. Idris2 base stdlib lacks JSON. Would need \`idris2-json\` dep or a custom parser. | | Core - ID generation uniqueness | Needs \`crypto.randomUUID()\`. Idris2 base lacks crypto. Would need crypto dep or deterministic-RNG mock (defeats the uniqueness check). | ## Patterns applied From \`panic-free-tests-and-benches/clade-registry/clade-A/idris2/PATTERNS.adoc\`: - \`ascii-only-string-literals-idris2\` - \`one-mega-list-for-testcases-idris2\` **New learning** (worth a registry PR follow-up): Idris2's \`head\` requires NonEmpty proof. Use cons-cell pattern match for safe first-element access: \`\`\`idris2 case xs of (h :: _) => h [] => default \`\`\` ## Why partial ubicity's other 3 TS files include SHA-256 hashing (privacy.test.ts), JSON parsing (export.test.ts), and file mapping logic (mapper.test.ts) that require substantial Idris2 dependencies or a different port strategy entirely. The ESTATE-ROLLOUT.adoc \"unit-logic strategy\" section flags this — each repo needs a decision (Idris2 mirror / canonical-language port / delete) before more porting work. This PR is the smallest defensible unit: one TS file's worth of straightforward data-validation tests. The remaining files are tracked separately. ## Test plan - [x] \`just test-core\` runs end-to-end and exits 0. - [x] 3/3 PASS verified locally. - [x] \`tests/core.test.ts\` deleted; other test files retained. - [ ] Reviewer: confirm \`just test-core\` works in your environment (idris2 0.8.0). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ae9a5a0 commit d1c150a

7 files changed

Lines changed: 468 additions & 95 deletions

File tree

Justfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import? "contractile.just"
77
default:
88
@just --list
99

10+
# Run the Idris2 test suite (estate port 6/11; partial — core.test.ts only).
11+
# Requires idris2 0.8.0+ on PATH.
12+
test-core:
13+
@export IDRIS2_PREFIX="$(dirname "$(dirname "$(command -v idris2)")")" && \
14+
idris2 --build ubicity-tests.ipkg && \
15+
./build/exec/ubicity-tests
16+
1017
# Setup development environment
1118
setup:
1219
@echo "🔧 Setting up UbiCity development environment..."

tests/core.test.ts

Lines changed: 0 additions & 95 deletions
This file was deleted.

tests/idris2/CoreTest.idr

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
-- SPDX-License-Identifier: PMPL-1.0-or-later
2+
-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
--
4+
-- Port of tests/core.test.ts to Idris2, estate-rollout port 6/11.
5+
-- PARTIAL: 1 of 4 TS test files in ubicity. The other 3 (privacy,
6+
-- mapper, export) await strategy review per ESTATE-ROLLOUT.adoc
7+
-- "unit-logic strategy" section.
8+
--
9+
-- 3 of 5 original tests ported here. The other 2 deferred:
10+
--
11+
-- • "Core - Data persistence" — needs JSON parse + temp-file write.
12+
-- Idris2 base stdlib lacks JSON. Would need either a JSON
13+
-- dependency or a custom parser. Deferred.
14+
--
15+
-- • "Core - ID generation uniqueness" — needs crypto.randomUUID().
16+
-- Idris2 base lacks crypto. Would need either a crypto dep or
17+
-- a deterministic-RNG mock (which defeats the uniqueness check).
18+
-- Deferred.
19+
20+
module CoreTest
21+
22+
import Test.Spec
23+
import Data.String
24+
import Data.List
25+
26+
%default covering
27+
28+
-- == LearningExperience record type ==
29+
-- Mirrors the TS interface implicitly assumed by core.test.ts. Fields
30+
-- match the literal object the TS test constructs.
31+
32+
record Location where
33+
constructor MkLocation
34+
name : String
35+
ltype : String
36+
37+
record Learner where
38+
constructor MkLearner
39+
id : String
40+
name : String
41+
42+
record Experience where
43+
constructor MkExperience
44+
etype : String
45+
domain : List String
46+
description : String
47+
48+
record LearningExperience where
49+
constructor MkLearningExperience
50+
id : String
51+
timestamp : String
52+
learner : Learner
53+
context_location : Location
54+
experience : Experience
55+
56+
-- Sample valid experience matching the TS literal object.
57+
sampleExperience : LearningExperience
58+
sampleExperience = MkLearningExperience
59+
{ id = "test-001"
60+
, timestamp = "2026-05-20T00:00:00Z"
61+
, learner = MkLearner
62+
{ id = "learner-123"
63+
, name = "Test Learner"
64+
}
65+
, context_location = MkLocation
66+
{ name = "Test Makerspace"
67+
, ltype = "makerspace"
68+
}
69+
, experience = MkExperience
70+
{ etype = "workshop"
71+
, domain = ["electronics", "art"]
72+
, description = "Built a light-up sculpture"
73+
}
74+
}
75+
76+
-- == ISO 8601 timestamp shape helpers ==
77+
78+
isoLooksValid : String -> Bool
79+
isoLooksValid s = isInfixOf "T" s && isInfixOf "Z" s
80+
81+
-- == List dedup helper ==
82+
83+
dedupList : Eq a => List a -> List a
84+
dedupList = nub
85+
86+
-- == Tests ==
87+
88+
public export
89+
allSuites : List TestCase
90+
allSuites =
91+
[ test "Core - LearningExperience validation (field presence)" $ do
92+
let e = sampleExperience
93+
allPass
94+
[ assertTrue "id non-empty" (length e.id > 0)
95+
, assertTrue "learner id non-empty" (length e.learner.id > 0)
96+
, assertTrue "location name non-empty" (length e.context_location.name > 0)
97+
, assertTrue "domain has exactly 2 entries" (length e.experience.domain == 2)
98+
]
99+
100+
, test "Core - Timestamp ISO 8601 shape (T + Z)" $ do
101+
let s = "2026-05-20T10:30:00Z"
102+
let parseable = length s >= 20
103+
allPass
104+
[ assertTrue "contains T" (isInfixOf "T" s)
105+
, assertTrue "contains Z" (isInfixOf "Z" s)
106+
, assertTrue "length >= 20" parseable
107+
]
108+
109+
, test "Core - Domain array dedup/normalize/filter" $ do
110+
let domains = ["electronics", "art", "sculpture"]
111+
let unique = dedupList domains
112+
let normalized = map toLower domains
113+
let filtered = filter (isPrefixOf "e") domains
114+
let first_normalized = case normalized of
115+
(x :: _) => x
116+
[] => ""
117+
allPass
118+
[ assertTrue "dedup keeps all 3" (length unique == 3)
119+
, assertTrue "normalize keeps electronics" (length normalized == 3 && first_normalized == "electronics")
120+
, assertTrue "filter on e-prefix returns 1" (length filtered == 1)
121+
]
122+
]

tests/idris2/Main.idr

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-- SPDX-License-Identifier: PMPL-1.0-or-later
2+
-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
4+
module Main
5+
6+
import Test.Spec
7+
import CoreTest
8+
import MapperTest
9+
import System
10+
11+
%default covering
12+
13+
main : IO ()
14+
main = do
15+
(p1, f1) <- runTestSuite "CoreTest" CoreTest.allSuites
16+
(p2, f2) <- runTestSuite "MapperTest" MapperTest.allSuites
17+
let totalPassed = p1 + p2
18+
let totalFailed = f1 + f2
19+
putStrLn ""
20+
putStrLn $ "=== Total: " ++ show totalPassed ++ " passed, " ++ show totalFailed ++ " failed ==="
21+
if totalFailed > 0
22+
then exitWith (ExitFailure 1)
23+
else pure ()

0 commit comments

Comments
 (0)