git.iko.soy/rowtype-yoga/purescript-yoga-fetch-om
on main help sign in
Migrate to purescript-spec, derive test server from API type
commit71b0fbce636c2093f0de92eca5970b069a50c47b authorMark <mark.eibes@gmail.com> dateMon 9 Feb 2026, 19:46 (8 months ago) parent5ad183e
14 files changed+440−321
.purslint-cache/props.db package.json+1−1 spago.lock+250−75 spago.yaml+4−2 src/Yoga/Fetch/Om/ExtractParams.purs+7 test/BuildUrl.Spec.purs+86−105 test/MakeRequest.Spec.purs+12−18 test/Polymorphic.Spec.purs+38−28 test/SplitParams.Spec.purs+22−31 test/Test.Main.purs−24 test/Test/Main.purs+14 test/Variant.Spec.purs+6−11 test/setup.js−16 vitest.config.js−10
.purslint-cache/props.dbnew file

Binary file not shown.

package.json+1−1
@@ -5,7 +5,7 @@
55 "type": "module",
66 "scripts": {
77 "build": "spago build",
8− "test": "spago build && bunx vitest run",
8+ "test": "spago test",
99 "clean": "rm -rf output .spago",
1010 "watch": "spago build --watch",
1111 "repl": "spago repl",
spago.lock+250−75
@@ -76,7 +76,10 @@
7676 "refs": ">=6.0.0 <7.0.0"
7777 },
7878 {
79− "vitest": ">=1.0.0 <3.0.0"
79+ "spec": ">=8.0.0 <9.0.0"
80+ },
81+ {
82+ "spec-discovery": ">=8.0.0 <9.0.0"
8083 },
8184 {
8285 "yoga-fastify-om": ">=0.1.0 <0.2.0"
@@ -679,7 +682,9 @@
679682 "extra_packages": {
680683 "yoga-fastify": "0.2.3",
681684 "yoga-fastify-om": "0.1.0",
682− "yoga-http-api": "0.1.4",
685+ "yoga-http-api": {
686+ "path": "../purescript-yoga-http-api"
687+ },
683688 "yoga-json": "5.2.0",
684689 "yoga-om": "1.2.2",
685690 "yoga-om-layer": "1.0.6",
@@ -719,6 +724,47 @@
719724 "prelude"
720725 ]
721726 },
727+ "argonaut-codecs": {
728+ "type": "registry",
729+ "version": "9.1.0",
730+ "integrity": "sha256-K910SBrmYallESsm8pxJYThs7lig6hxX3M4PW33Cgew=",
731+ "dependencies": [
732+ "argonaut-core",
733+ "arrays",
734+ "bifunctors",
735+ "either",
736+ "foldable-traversable",
737+ "foreign-object",
738+ "identity",
739+ "integers",
740+ "lists",
741+ "maybe",
742+ "nonempty",
743+ "ordered-collections",
744+ "prelude",
745+ "record",
746+ "strings",
747+ "tuples"
748+ ]
749+ },
750+ "argonaut-core": {
751+ "type": "registry",
752+ "version": "7.0.0",
753+ "integrity": "sha256-gYZihtBIKthVuXXoiPphMookSf8M5aHyFYkcIWqlxTM=",
754+ "dependencies": [
755+ "arrays",
756+ "control",
757+ "either",
758+ "foreign-object",
759+ "functions",
760+ "gen",
761+ "maybe",
762+ "nonempty",
763+ "prelude",
764+ "strings",
765+ "tailrec"
766+ ]
767+ },
722768 "arraybuffer-types": {
723769 "type": "registry",
724770 "version": "3.0.2",
@@ -916,6 +962,16 @@
916962 "unsafe-coerce"
917963 ]
918964 },
965+ "exitcodes": {
966+ "type": "registry",
967+ "version": "4.0.0",
968+ "integrity": "sha256-xBl4aFSEQ05Owp1ZvO6OxxvLK3LVzU35RSbj79SsePQ=",
969+ "dependencies": [
970+ "enums",
971+ "maybe",
972+ "prelude"
973+ ]
974+ },
919975 "foldable-traversable": {
920976 "type": "registry",
921977 "version": "6.0.0",
@@ -1297,6 +1353,91 @@
12971353 "unsafe-coerce"
12981354 ]
12991355 },
1356+ "node-event-emitter": {
1357+ "type": "registry",
1358+ "version": "3.0.0",
1359+ "integrity": "sha256-wodx71NxJBPXOaawFn01V1ByYB2vT+I3RuV2giOVKMg=",
1360+ "dependencies": [
1361+ "effect",
1362+ "either",
1363+ "functions",
1364+ "maybe",
1365+ "nullable",
1366+ "prelude",
1367+ "unsafe-coerce"
1368+ ]
1369+ },
1370+ "node-fs": {
1371+ "type": "registry",
1372+ "version": "9.2.0",
1373+ "integrity": "sha256-fxioTSm9y47WuYs9F/7nxCh/RVDCl3Kr3Hg779J4SJA=",
1374+ "dependencies": [
1375+ "aff",
1376+ "datetime",
1377+ "effect",
1378+ "either",
1379+ "enums",
1380+ "exceptions",
1381+ "functions",
1382+ "integers",
1383+ "js-date",
1384+ "maybe",
1385+ "node-buffer",
1386+ "node-path",
1387+ "node-streams",
1388+ "nullable",
1389+ "partial",
1390+ "prelude",
1391+ "strings"
1392+ ]
1393+ },
1394+ "node-path": {
1395+ "type": "registry",
1396+ "version": "5.0.1",
1397+ "integrity": "sha256-j7n/SmpVz0FOMJl4XZop3ofJ+MeIPkMNqUEUKHEL6Us=",
1398+ "dependencies": [
1399+ "effect"
1400+ ]
1401+ },
1402+ "node-process": {
1403+ "type": "registry",
1404+ "version": "11.2.0",
1405+ "integrity": "sha256-onzKeNmaeYfN3HSDPqSFTrrP2Yl9tnVsfKKhnf2plxM=",
1406+ "dependencies": [
1407+ "effect",
1408+ "exceptions",
1409+ "foreign",
1410+ "foreign-object",
1411+ "maybe",
1412+ "node-event-emitter",
1413+ "node-streams",
1414+ "nullable",
1415+ "posix-types",
1416+ "prelude",
1417+ "strings"
1418+ ]
1419+ },
1420+ "node-streams": {
1421+ "type": "registry",
1422+ "version": "9.0.1",
1423+ "integrity": "sha256-yuVGm/R/tBr4Nphi+a1a984Z0fkmN9Q5wWtSmbmJTpA=",
1424+ "dependencies": [
1425+ "aff",
1426+ "arrays",
1427+ "effect",
1428+ "either",
1429+ "exceptions",
1430+ "maybe",
1431+ "node-buffer",
1432+ "node-event-emitter",
1433+ "nullable",
1434+ "prelude",
1435+ "refs",
1436+ "st",
1437+ "tailrec",
1438+ "unsafe-coerce"
1439+ ]
1440+ },
13001441 "nonempty": {
13011442 "type": "registry",
13021443 "version": "7.0.0",
@@ -1340,6 +1481,56 @@
13401481 "prelude"
13411482 ]
13421483 },
1484+ "open-memoize": {
1485+ "type": "registry",
1486+ "version": "6.2.0",
1487+ "integrity": "sha256-yJLuVYX8FocHIJ+6q7efH/Pj4S+e1Y57DZ5Dn7qf4ww=",
1488+ "dependencies": [
1489+ "either",
1490+ "integers",
1491+ "lazy",
1492+ "lists",
1493+ "maybe",
1494+ "partial",
1495+ "prelude",
1496+ "strings",
1497+ "tuples"
1498+ ]
1499+ },
1500+ "optparse": {
1501+ "type": "registry",
1502+ "version": "5.0.1",
1503+ "integrity": "sha256-2btDjHPRR0uujUQJuZAN9AzvVoXe7yG6IwnY5evERhQ=",
1504+ "dependencies": [
1505+ "arrays",
1506+ "bifunctors",
1507+ "control",
1508+ "effect",
1509+ "either",
1510+ "enums",
1511+ "exists",
1512+ "exitcodes",
1513+ "foldable-traversable",
1514+ "free",
1515+ "integers",
1516+ "lazy",
1517+ "lists",
1518+ "maybe",
1519+ "newtype",
1520+ "node-buffer",
1521+ "node-process",
1522+ "node-streams",
1523+ "nonempty",
1524+ "numbers",
1525+ "open-memoize",
1526+ "partial",
1527+ "prelude",
1528+ "strings",
1529+ "tailrec",
1530+ "transformers",
1531+ "tuples"
1532+ ]
1533+ },
13431534 "ordered-collections": {
13441535 "type": "registry",
13451536 "version": "3.2.0",
@@ -1415,6 +1606,16 @@
14151606 "tuples"
14161607 ]
14171608 },
1609+ "posix-types": {
1610+ "type": "registry",
1611+ "version": "6.0.0",
1612+ "integrity": "sha256-Je5jG439F/SFO/IvVevZnWVxDIonrehlKuJkefP2lHE=",
1613+ "dependencies": [
1614+ "maybe",
1615+ "newtype",
1616+ "prelude"
1617+ ]
1618+ },
14181619 "prelude": {
14191620 "type": "registry",
14201621 "version": "6.0.2",
@@ -1531,6 +1732,51 @@
15311732 "tuples"
15321733 ]
15331734 },
1735+ "spec-discovery": {
1736+ "type": "registry",
1737+ "version": "8.4.0",
1738+ "integrity": "sha256-aFp8bllT/wrxQu8hMlDchQQY08dQAz+Xdq2hZi5z3yY=",
1739+ "dependencies": [
1740+ "aff",
1741+ "effect",
1742+ "foldable-traversable",
1743+ "prelude",
1744+ "spec",
1745+ "spec-node"
1746+ ]
1747+ },
1748+ "spec-node": {
1749+ "type": "registry",
1750+ "version": "0.0.3",
1751+ "integrity": "sha256-8/9+g0obbamV0YtqzmttbWs8HaWF7CEeW+eDtqzuAAo=",
1752+ "dependencies": [
1753+ "aff",
1754+ "argonaut-codecs",
1755+ "argonaut-core",
1756+ "arrays",
1757+ "control",
1758+ "datetime",
1759+ "effect",
1760+ "either",
1761+ "foldable-traversable",
1762+ "identity",
1763+ "integers",
1764+ "maybe",
1765+ "newtype",
1766+ "node-buffer",
1767+ "node-fs",
1768+ "node-process",
1769+ "now",
1770+ "numbers",
1771+ "optparse",
1772+ "ordered-collections",
1773+ "partial",
1774+ "prelude",
1775+ "spec",
1776+ "strings",
1777+ "tuples"
1778+ ]
1779+ },
15341780 "st": {
15351781 "type": "registry",
15361782 "version": "6.2.0",
@@ -1711,41 +1957,6 @@
17111957 "unsafe-coerce"
17121958 ]
17131959 },
1714− "vitest": {
1715− "type": "registry",
1716− "version": "2.0.0",
1717− "integrity": "sha256-GkH1eyMaDyDUSC7kRSJrrD6k397m/SKQcKauVRmZzRI=",
1718− "dependencies": [
1719− "aff",
1720− "effect",
1721− "either",
1722− "exceptions",
1723− "js-promise",
1724− "js-promise-aff",
1725− "maybe",
1726− "nullable",
1727− "prelude",
1728− "transformers",
1729− "unsafe-coerce",
1730− "web-dom",
1731− "web-html"
1732− ]
1733− },
1734− "web-dom": {
1735− "type": "registry",
1736− "version": "6.0.0",
1737− "integrity": "sha256-rVeqkxChkjqisDvOujy9BMEgGqJkwX3t6g21bXfiG5o=",
1738− "dependencies": [
1739− "effect",
1740− "enums",
1741− "maybe",
1742− "newtype",
1743− "nullable",
1744− "prelude",
1745− "unsafe-coerce",
1746− "web-events"
1747− ]
1748− },
17491960 "web-events": {
17501961 "type": "registry",
17511962 "version": "4.0.0",
@@ -1785,41 +1996,6 @@
17851996 "web-events"
17861997 ]
17871998 },
1788− "web-html": {
1789− "type": "registry",
1790− "version": "4.1.1",
1791− "integrity": "sha256-yOuZJGUxFrivHKpo/jKuKzFZaq6PPPE64ftSysXFssk=",
1792− "dependencies": [
1793− "effect",
1794− "enums",
1795− "foreign",
1796− "functions",
1797− "js-date",
1798− "maybe",
1799− "media-types",
1800− "newtype",
1801− "nullable",
1802− "prelude",
1803− "unsafe-coerce",
1804− "web-dom",
1805− "web-events",
1806− "web-file",
1807− "web-storage"
1808− ]
1809− },
1810− "web-storage": {
1811− "type": "registry",
1812− "version": "5.0.0",
1813− "integrity": "sha256-YJfos55oeKtBStNfoWfeflSy8j+4IDym5eaZtIY7YM4=",
1814− "dependencies": [
1815− "effect",
1816− "maybe",
1817− "nullable",
1818− "prelude",
1819− "unsafe-coerce",
1820− "web-events"
1821− ]
1822− },
18231999 "web-streams": {
18242000 "type": "registry",
18252001 "version": "4.0.0",
@@ -1894,9 +2070,8 @@
18942070 ]
18952071 },
18962072 "yoga-http-api": {
1897− "type": "registry",
1898− "version": "0.1.4",
1899− "integrity": "sha256-kFnDMt0KGsi+84RKgmLQEr9HB+XHOgCeLMsFatCeCiY=",
2073+ "type": "local",
2074+ "path": "../purescript-yoga-http-api",
19002075 "dependencies": [
19012076 "aff",
19022077 "arrays",
spago.yaml+4−2
@@ -4,7 +4,8 @@ workspace:
44 extraPackages:
55 yoga-fastify-om: "0.1.0"
66 yoga-fastify: "0.2.3"
7− yoga-http-api: "0.1.4"
7+ yoga-http-api:
8+ path: ../purescript-yoga-http-api
89 yoga-om: "1.2.2"
910 yoga-om-layer: "1.0.6"
1011 yoga-om-strom: "0.2.0"
@@ -49,7 +50,8 @@ package:
4950 test:
5051 main: Test.Main
5152 dependencies:
52− - vitest: ">=1.0.0 <3.0.0"
53+ - spec: ">=8.0.0 <9.0.0"
54+ - spec-discovery: ">=8.0.0 <9.0.0"
5355 - console: ">=6.0.0 <7.0.0"
5456 - yoga-fastify-om: ">=0.1.0 <0.2.0"
5557 - refs: ">=6.0.0 <7.0.0"
src/Yoga/Fetch/Om/ExtractParams.purs+7
@@ -10,6 +10,7 @@ import Data.Unit (Unit)
1010import Prim.RowList (class RowToList, RowList)
1111import Prim.RowList as RL
1212import Yoga.HTTP.API.Route.Encoding (JSON, FormData, NoBody)
13+import Yoga.HTTP.API.Route.Handler (Request)
1314
1415class ExtractRequestBody :: Type -> Type -> Constraint
1516class ExtractRequestBody request body | request -> body
@@ -20,6 +21,9 @@ instance
2021 , UnwrapEncoding encoding body
2122 ) =>
2223 ExtractRequestBody (Record row) body
24+else instance
25+ ExtractRequestBody (Record row) body =>
26+ ExtractRequestBody (Request (Record row)) body
2327
2428class ExtractRequestHeaders :: Type -> Row Type -> Constraint
2529class ExtractRequestHeaders request headers | request -> headers
@@ -29,6 +33,9 @@ instance
2933 , FindHeaders rl headers
3034 ) =>
3135 ExtractRequestHeaders (Record row) headers
36+else instance
37+ ExtractRequestHeaders (Record row) headers =>
38+ ExtractRequestHeaders (Request (Record row)) headers
3239
3340class FindBodyType :: RowList Type -> Type -> Constraint
3441class FindBodyType rl encoding | rl -> encoding
test/BuildUrl.Spec.purs+86−105
@@ -3,116 +3,97 @@ module BuildUrl.Spec where
33import Prelude
44
55import Data.Maybe (Maybe(..))
6−import Effect (Effect)
7−import Effect.Aff (Aff)
6+import Test.Spec (Spec, describe, it)
7+import Test.Spec.Assertions (shouldEqual)
88import Type.Proxy (Proxy(..))
99import Yoga.Fetch.Om.BuildUrl (buildUrl, substitutePathParams, appendQueryParams)
1010import Yoga.HTTP.API.Path (Path, type (/), type (:))
11−import ViTest (ViTest, describe, test)
12−import ViTest.Expect (expectToBe)
13−
14−expectToEqual :: forall a. Eq a => a -> a -> Aff Unit
15−expectToEqual expected actual = expectToBe true (expected == actual)
1611
1712--------------------------------------------------------------------------------
1813-- SubstitutePathParams Tests
1914--------------------------------------------------------------------------------
2015
21−testSubstitutePathParams :: Effect ViTest
22−testSubstitutePathParams = describe "substitutePathParams" $ do
23− _ <- test "substitutes single path param" do
24− let
25− result = substitutePathParams @(id :: Int) { id: 42 } "/users/:id"
26− expectToEqual "/users/42" result
27−
28− _ <- test "substitutes multiple path params" do
29− let
30− result = substitutePathParams @(userId :: Int, postId :: Int)
31− { userId: 1, postId: 99 }
32− "/users/:userId/posts/:postId"
33− expectToEqual "/users/1/posts/99" result
34−
35− _ <- test "handles String params" do
36− let
37− result = substitutePathParams @(slug :: String)
38− { slug: "hello-world" }
39− "/posts/:slug"
40− expectToEqual "/posts/hello-world" result
41−
42− test "handles empty params" do
43− let
44− result = substitutePathParams @() {} "/users"
45− expectToEqual "/users" result
46−
47−--------------------------------------------------------------------------------
48−-- AppendQueryParams Tests
49−--------------------------------------------------------------------------------
50−
51−testAppendQueryParams :: Effect ViTest
52−testAppendQueryParams = describe "appendQueryParams" $ do
53− _ <- test "appends single query param" do
54− let
55− result = appendQueryParams @(limit :: Int) { limit: 10 } "/users"
56− expectToEqual "/users?limit=10" result
57−
58− _ <- test "appends multiple query params" do
59− let
60− result = appendQueryParams @(limit :: Int, offset :: Int)
61− { limit: 10, offset: 20 }
62− "/users"
63− expectToEqual "/users?limit=10&offset=20" result
64−
65− _ <- test "handles Maybe params - Just" do
66− let
67− result = appendQueryParams @(limit :: Maybe Int)
68− { limit: Just 10 }
69− "/users"
70− expectToEqual "/users?limit=10" result
71−
72− _ <- test "handles Maybe params - Nothing" do
73− let
74− result = appendQueryParams @(limit :: Maybe Int)
75− { limit: Nothing }
76− "/users"
77− expectToEqual "/users" result
78−
79− _ <- test "handles mixed Maybe and required params" do
80− let
81− result = appendQueryParams @(limit :: Maybe Int, offset :: Int)
82− { limit: Nothing, offset: 20 }
83− "/users"
84− expectToEqual "/users?offset=20" result
85−
86− _ <- test "handles empty query params" do
87− let
88− result = appendQueryParams @() {} "/users"
89− expectToEqual "/users" result
90−
91− test "URL-encodes special characters in values" do
92− let
93− result = appendQueryParams @(search :: String) { search: "hello world&more=yes" } "/users"
94− expectToEqual "/users?search=hello%20world%26more%3Dyes" result
95−
96−--------------------------------------------------------------------------------
97−-- BuildUrl Tests
98−--------------------------------------------------------------------------------
99−
100−testBuildUrl :: Effect ViTest
101−testBuildUrl = describe "buildUrl" $ do
102− _ <- test "builds URL with path params only" do
103− let
104− result = buildUrl
105− "https://api.example.com"
106− (Proxy :: _ (Path ("users" / "id" : Int)))
107− { id: 42 }
108− {}
109− expectToEqual "https://api.example.com/users/42" result
110−
111− test "builds URL with query params only" do
112− let
113− result = buildUrl
114− "https://api.example.com"
115− (Proxy :: _ (Path "users"))
116− {}
117− { limit: 10, offset: 20 }
118− expectToEqual "https://api.example.com/users?limit=10&offset=20" result
16+spec :: Spec Unit
17+spec = do
18+ describe "substitutePathParams" do
19+ it "substitutes single path param" do
20+ let result = substitutePathParams @(id :: Int) { id: 42 } "/users/:id"
21+ result `shouldEqual` "/users/42"
22+
23+ it "substitutes multiple path params" do
24+ let
25+ result = substitutePathParams @(userId :: Int, postId :: Int)
26+ { userId: 1, postId: 99 }
27+ "/users/:userId/posts/:postId"
28+ result `shouldEqual` "/users/1/posts/99"
29+
30+ it "handles String params" do
31+ let
32+ result = substitutePathParams @(slug :: String)
33+ { slug: "hello-world" }
34+ "/posts/:slug"
35+ result `shouldEqual` "/posts/hello-world"
36+
37+ it "handles empty params" do
38+ let result = substitutePathParams @() {} "/users"
39+ result `shouldEqual` "/users"
40+
41+ describe "appendQueryParams" do
42+ it "appends single query param" do
43+ let result = appendQueryParams @(limit :: Int) { limit: 10 } "/users"
44+ result `shouldEqual` "/users?limit=10"
45+
46+ it "appends multiple query params" do
47+ let
48+ result = appendQueryParams @(limit :: Int, offset :: Int)
49+ { limit: 10, offset: 20 }
50+ "/users"
51+ result `shouldEqual` "/users?limit=10&offset=20"
52+
53+ it "handles Maybe params - Just" do
54+ let
55+ result = appendQueryParams @(limit :: Maybe Int)
56+ { limit: Just 10 }
57+ "/users"
58+ result `shouldEqual` "/users?limit=10"
59+
60+ it "handles Maybe params - Nothing" do
61+ let
62+ result = appendQueryParams @(limit :: Maybe Int)
63+ { limit: Nothing }
64+ "/users"
65+ result `shouldEqual` "/users"
66+
67+ it "handles mixed Maybe and required params" do
68+ let
69+ result = appendQueryParams @(limit :: Maybe Int, offset :: Int)
70+ { limit: Nothing, offset: 20 }
71+ "/users"
72+ result `shouldEqual` "/users?offset=20"
73+
74+ it "handles empty query params" do
75+ let result = appendQueryParams @() {} "/users"
76+ result `shouldEqual` "/users"
77+
78+ it "URL-encodes special characters in values" do
79+ let result = appendQueryParams @(search :: String) { search: "hello world&more=yes" } "/users"
80+ result `shouldEqual` "/users?search=hello%20world%26more%3Dyes"
81+
82+ describe "buildUrl" do
83+ it "builds URL with path params only" do
84+ let
85+ result = buildUrl
86+ "https://api.example.com"
87+ (Proxy :: _ (Path ("users" / "id" : Int)))
88+ { id: 42 }
89+ {}
90+ result `shouldEqual` "https://api.example.com/users/42"
91+
92+ it "builds URL with query params only" do
93+ let
94+ result = buildUrl
95+ "https://api.example.com"
96+ (Proxy :: _ (Path "users"))
97+ {}
98+ { limit: 10, offset: 20 }
99+ result `shouldEqual` "https://api.example.com/users?limit=10&offset=20"
test/MakeRequest.Spec.purs+12−18
@@ -3,35 +3,29 @@ module MakeRequest.Spec where
33import Prelude
44
55import Data.Tuple.Nested ((/\))
6−import Effect (Effect)
7−import Effect.Aff (Aff)
86import JS.Fetch.Headers as Headers
97import Prim.RowList as RL
8+import Test.Spec (Spec, describe, it)
9+import Test.Spec.Assertions (shouldEqual, shouldSatisfy)
1010import Type.Proxy (Proxy(..))
1111import Yoga.Fetch.Om (toHeaders)
12−import ViTest (ViTest, describe, test)
13−import ViTest.Expect (expectToBe)
1412
15−expectToEqual :: forall a. Eq a => a -> a -> Aff Unit
16−expectToEqual expected actual = expectToBe true (expected == actual)
17−
18−testToHeaders :: Effect ViTest
19−testToHeaders = describe "toHeaders" $ do
20− _ <- test "empty headers produces no entries" do
13+spec :: Spec Unit
14+spec = describe "toHeaders" do
15+ it "empty headers produces no entries" do
2116 let hdrs = toHeaders (Proxy :: _ RL.Nil) {}
22− expectToEqual [] (Headers.toArray hdrs)
17+ Headers.toArray hdrs `shouldEqual` []
2318
24− _ <- test "single header is included" do
19+ it "single header is included" do
2520 let hdrs = toHeaders (Proxy :: _ (RL.Cons "authorization" String RL.Nil)) { authorization: "Bearer abc123" }
26− expectToEqual [ "authorization" /\ "Bearer abc123" ] (Headers.toArray hdrs)
21+ Headers.toArray hdrs `shouldEqual` [ "authorization" /\ "Bearer abc123" ]
2722
28− test "multiple headers are included" do
23+ it "multiple headers are included" do
2924 let
3025 hdrs = toHeaders
3126 (Proxy :: _ (RL.Cons "authorization" String (RL.Cons "x-custom" String RL.Nil)))
3227 { authorization: "Bearer abc123", "x-custom": "value" }
3328 arr = Headers.toArray hdrs
34− expectToEqual true
35− ( arr == [ "authorization" /\ "Bearer abc123", "x-custom" /\ "value" ]
36− || arr == [ "x-custom" /\ "value", "authorization" /\ "Bearer abc123" ]
37− )
29+ arr `shouldSatisfy` \a ->
30+ a == [ "authorization" /\ "Bearer abc123", "x-custom" /\ "value" ]
31+ || a == [ "x-custom" /\ "value", "authorization" /\ "Bearer abc123" ]
test/Polymorphic.Spec.purs+38−28
@@ -2,18 +2,21 @@ module Polymorphic.Spec where
22
33import Prelude
44
5−import Effect (Effect)
5+import Effect.Aff (bracket)
6+import Effect.Aff.Class (liftAff)
67import Effect.Class (liftEffect)
7−import Effect.Ref as Ref
8−import Yoga.Fetch.Om (GET, Route, Path, type (/), type (:), client)
8+import Test.Spec (Spec, around, describe, it)
9+import Test.Spec.Assertions (shouldEqual)
10+import Yoga.Fastify.Fastify as F
11+import Yoga.Fastify.Fastify (Port(..), Host(..))
12+import Yoga.Fastify.Om.Route (Handler, Request, handle, respond, handleRoute)
13+import Yoga.Fetch.Om (GET, Route, type (/), type (:), client)
914import Yoga.Om (Om, ask, runOm)
10−import ViTest (ViTest, describe, test)
11−import ViTest.Expect (expectToBe)
1215
1316type User = { id :: Int, name :: String }
1417
1518type TestAPI =
16− { getUser :: Route GET (Path ("users" / "id" : Int)) {} (ok :: { body :: User })
19+ { getUser :: Route GET ("users" / "id" : Int) (Request {}) (ok :: { body :: User })
1720 }
1821
1922api
@@ -22,39 +25,46 @@ api
2225 }
2326api = client @TestAPI "http://localhost:44931"
2427
25−testPolymorphic :: Effect ViTest
26−testPolymorphic = describe "polymorphic client" do
27− _ <- test "fetches user with { token :: String } context" do
28− tokenRef <- liftEffect $ Ref.new ""
28+spec :: Spec Unit
29+spec = around withServer $ describe "polymorphic client" do
30+ it "fetches user with { token :: String } context" \_ ->
2931 runOm { token: "secret123" } { exception: \_ -> pure unit } do
3032 ctx <- ask
31− liftEffect $ Ref.write ctx.token tokenRef
3233 user <- api.getUser { id: 42 }
33− liftEffect $ Ref.write (ctx.token <> ":" <> user.name) tokenRef
34− val <- liftEffect $ Ref.read tokenRef
35− expectToBe val "secret123:User 42"
34+ liftAff $ (ctx.token <> ":" <> user.name) `shouldEqual` "secret123:User 42"
3635
37− _ <- test "fetches user with { apiKey :: String, userId :: Int } context" do
38− resultRef <- liftEffect $ Ref.new ""
36+ it "fetches user with { apiKey :: String, userId :: Int } context" \_ ->
3937 runOm { apiKey: "mykey", userId: 7 } { exception: \_ -> pure unit } do
4038 ctx <- ask
4139 user <- api.getUser { id: ctx.userId }
42− liftEffect $ Ref.write (ctx.apiKey <> ":" <> user.name) resultRef
43− val <- liftEffect $ Ref.read resultRef
44− expectToBe val "mykey:User 7"
40+ liftAff $ (ctx.apiKey <> ":" <> user.name) `shouldEqual` "mykey:User 7"
4541
46− test "same api.getUser works in two different contexts" do
47− ref1 <- liftEffect $ Ref.new ""
48− ref2 <- liftEffect $ Ref.new ""
42+ it "same api.getUser works in two different contexts" \_ -> do
4943 runOm { token: "tok1" } { exception: \_ -> pure unit } do
5044 ctx <- ask
5145 user <- api.getUser { id: 1 }
52− liftEffect $ Ref.write (ctx.token <> ":" <> user.name) ref1
46+ liftAff $ (ctx.token <> ":" <> user.name) `shouldEqual` "tok1:User 1"
5347 runOm { endpoint: "prod", region: "eu" } { exception: \_ -> pure unit } do
5448 ctx <- ask
5549 user <- api.getUser { id: 2 }
56− liftEffect $ Ref.write (ctx.endpoint <> "-" <> ctx.region <> ":" <> user.name) ref2
57− val1 <- liftEffect $ Ref.read ref1
58− val2 <- liftEffect $ Ref.read ref2
59− expectToBe val1 "tok1:User 1"
60− expectToBe val2 "prod-eu:User 2"
50+ liftAff $ (ctx.endpoint <> "-" <> ctx.region <> ":" <> user.name) `shouldEqual` "prod-eu:User 2"
51+
52+withServer :: forall a. (Unit -> _ a) -> _ a
53+withServer test = bracket acquire release (\_ -> test unit)
54+ where
55+ acquire = do
56+ fastify <- liftEffect do
57+ f <- F.fastify {}
58+ handleRoute getUserHandler f
59+ pure f
60+ void $ F.listen { port: Port 44931, host: Host "0.0.0.0" } fastify
61+ pure fastify
62+
63+ release = F.close
64+
65+type GetUserRoute = Route GET ("users" / "id" : Int) (Request {}) (ok :: { body :: User })
66+
67+getUserHandler :: Handler GetUserRoute
68+getUserHandler = handle do
69+ { path } <- ask
70+ respond @"ok" { id: path.id, name: "User " <> show path.id }
test/SplitParams.Spec.purs+22−31
@@ -2,22 +2,13 @@ module SplitParams.Spec where
22
33import Prelude
44
5−import Effect (Effect)
6−import Effect.Aff (Aff)
5+import Test.Spec (Spec, describe, it)
6+import Test.Spec.Assertions (shouldEqual)
77import Yoga.Fetch.Om.SplitParams (splitParams)
8−import ViTest (ViTest, describe, test)
9−import ViTest.Expect (expectToBe)
108
11−expectToEqual :: forall a. Eq a => a -> a -> Aff Unit
12−expectToEqual expected actual = expectToBe true (expected == actual)
13−
14−--------------------------------------------------------------------------------
15−-- SplitParams Tests
16−--------------------------------------------------------------------------------
17−
18−testSplitParams :: Effect ViTest
19−testSplitParams = describe "splitParams" $ do
20− _ <- test "splits path and query params" do
9+spec :: Spec Unit
10+spec = describe "splitParams" do
11+ it "splits path and query params" do
2112 let
2213 allParams = { id: 42, limit: 10, offset: 20 }
2314 result =
@@ -26,12 +17,12 @@ testSplitParams = describe "splitParams" $ do
2617 , query :: { limit :: Int, offset :: Int }
2718 , body :: {}
2819 }
29− expectToEqual 42 result.path.id
30− expectToEqual 10 result.query.limit
31− expectToEqual 20 result.query.offset
32− expectToEqual {} result.body
20+ result.path.id `shouldEqual` 42
21+ result.query.limit `shouldEqual` 10
22+ result.query.offset `shouldEqual` 20
23+ result.body `shouldEqual` {}
3324
34− _ <- test "splits path and body params" do
25+ it "splits path and body params" do
3526 let
3627 allParams = { id: 42, name: "Alice", email: "alice@example.com" }
3728 result =
@@ -40,12 +31,12 @@ testSplitParams = describe "splitParams" $ do
4031 , query :: {}
4132 , body :: { name :: String, email :: String }
4233 }
43− expectToEqual 42 result.path.id
44− expectToEqual {} result.query
45− expectToEqual "Alice" result.body.name
46− expectToEqual "alice@example.com" result.body.email
34+ result.path.id `shouldEqual` 42
35+ result.query `shouldEqual` {}
36+ result.body.name `shouldEqual` "Alice"
37+ result.body.email `shouldEqual` "alice@example.com"
4738
48− _ <- test "splits all three param types" do
39+ it "splits all three param types" do
4940 let
5041 allParams =
5142 { id: 42
@@ -58,11 +49,11 @@ testSplitParams = describe "splitParams" $ do
5849 , query :: { limit :: Int }
5950 , body :: { name :: String }
6051 }
61− expectToEqual 42 result.path.id
62− expectToEqual 10 result.query.limit
63− expectToEqual "Bob" result.body.name
52+ result.path.id `shouldEqual` 42
53+ result.query.limit `shouldEqual` 10
54+ result.body.name `shouldEqual` "Bob"
6455
65− test "handles empty splits" do
56+ it "handles empty splits" do
6657 let
6758 allParams = { id: 42 }
6859 result =
@@ -71,6 +62,6 @@ testSplitParams = describe "splitParams" $ do
7162 , query :: {}
7263 , body :: {}
7364 }
74− expectToEqual 42 result.path.id
75− expectToEqual {} result.query
76− expectToEqual {} result.body
65+ result.path.id `shouldEqual` 42
66+ result.query `shouldEqual` {}
67+ result.body `shouldEqual` {}
test/Test.Main.pursdeleted−24
@@ -1,24 +0,0 @@
1−module Test.Main where
2−
3−import Prelude
4−
5−import BuildUrl.Spec as BuildUrl
6−import MakeRequest.Spec as MakeRequest
7−import Polymorphic.Spec as Polymorphic
8−import SplitParams.Spec as SplitParams
9−import Variant.Spec as Variant
10−import Effect (Effect)
11−import ViTest (ViTest, viTest)
12−
13−spec :: Effect ViTest
14−spec = do
15− _ <- BuildUrl.testSubstitutePathParams
16− _ <- BuildUrl.testAppendQueryParams
17− _ <- BuildUrl.testBuildUrl
18− _ <- SplitParams.testSplitParams
19− _ <- Variant.testOnly
20− _ <- MakeRequest.testToHeaders
21− Polymorphic.testPolymorphic
22−
23−main :: ViTest
24−main = viTest spec
test/Test/Main.pursnew file+14
@@ -0,0 +1,14 @@
1+module Test.Main where
2+
3+import Prelude
4+
5+import Effect (Effect)
6+import Effect.Aff (launchAff_)
7+import Test.Spec.Discovery (discover)
8+import Test.Spec.Reporter (consoleReporter)
9+import Test.Spec.Runner (runSpec)
10+
11+main :: Effect Unit
12+main = launchAff_ do
13+ specs <- discover ".*\\.Spec"
14+ runSpec [ consoleReporter ] specs
test/Variant.Spec.purs+6−11
@@ -4,21 +4,16 @@ import Prelude
44
55import Data.Variant (Variant)
66import Data.Variant as Variant
7−import Effect (Effect)
8−import Effect.Aff (Aff)
7+import Test.Spec (Spec, describe, it)
8+import Test.Spec.Assertions (shouldEqual)
99import Type.Proxy (Proxy(..))
10−import ViTest (ViTest, describe, test)
11−import ViTest.Expect (expectToBe)
1210import Yoga.Fetch.Om.Variant (only)
1311
14−expectToEqual :: forall a. Eq a => a -> a -> Aff Unit
15−expectToEqual expected actual = expectToBe true (expected == actual)
16−
17−testOnly :: Effect ViTest
18−testOnly = describe "only" do
19− test "extracts single variant value - record" do
12+spec :: Spec Unit
13+spec = describe "only" do
14+ it "extracts single variant value - record" do
2015 let
2116 variant :: Variant (ok :: { id :: Int, name :: String })
2217 variant = Variant.inj (Proxy :: _ "ok") { id: 42, name: "Alice" }
2318 result = only variant
24− expectToEqual { id: 42, name: "Alice" } result
19+ result `shouldEqual` { id: 42, name: "Alice" }
test/setup.jsdeleted−16
@@ -1,16 +0,0 @@
1−import {
2− mkServer,
3− listenServer,
4− closeServer,
5−} from "../output/TestServer/index.js";
6−
7−let fastify;
8−
9−export async function setup() {
10− fastify = mkServer();
11− await listenServer(fastify)();
12−}
13−
14−export async function teardown() {
15− await closeServer(fastify)();
16−}
vitest.config.jsdeleted−10
@@ -1,10 +0,0 @@
1−import { defineConfig } from "vitest/config";
2−
3−export default defineConfig({
4− test: {
5− include: ["output/Test.Main/index.js"],
6− environment: "jsdom",
7− globals: true,
8− globalSetup: ["test/setup.js"],
9− },
10−});