Skip to content

Commit 0342826

Browse files
committed
Merge branch 'master' of github.com:rescript-lang/rescript-lang.org into vlk/split-out-api-overview
2 parents c39224a + dfc4cdc commit 0342826

8 files changed

Lines changed: 405 additions & 5 deletions

File tree

app/routes.res

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ let stdlibPaths = {
1111
->Array.filter(path => path !== "docs/manual/api/stdlib")
1212
}
1313

14+
let domPaths = {
15+
let rawFile = await Node.Fs.readFile("./markdown-pages/docs/api/dom.json", "utf-8")
16+
let json = JSON.parseOrThrow(rawFile)
17+
switch json {
18+
| Object(json) => Dict.keysToArray(json)
19+
| _ => []
20+
}
21+
->Array.map(key => "docs/manual/api/" ++ key)
22+
->Array.filter(path => path !== "docs/manual/api/dom")
23+
}
24+
1425
let beltPaths = {
1526
let rawFile = await Node.Fs.readFile("./markdown-pages/docs/api/belt.json", "utf-8")
1627
let json = JSON.parseOrThrow(rawFile)
@@ -25,6 +36,9 @@ let beltPaths = {
2536
let stdlibRoutes =
2637
stdlibPaths->Array.map(path => route(path, "./routes/ApiRoute.jsx", ~options={id: path}))
2738

39+
let domRoutes =
40+
domPaths->Array.map(path => route(path, "./routes/ApiRoute.jsx", ~options={id: path}))
41+
2842
let beltRoutes =
2943
beltPaths->Array.map(path => route(path, "./routes/ApiRoute.jsx", ~options={id: path}))
3044

@@ -46,7 +60,9 @@ let mdxRoutes = mdxRoutes("./routes/MdxRoute.jsx")->Array.filter(r =>
4660
String.startsWith(path, "blog/") ||
4761
path === "community" ||
4862
String.startsWith(path, "community/") ||
49-
path === "docs/manual/api"
63+
path === "docs/manual/api" ||
64+
path === "community" ||
65+
String.startsWith(path, "community/")
5066
)
5167
->Option.getOr(false)
5268
)
@@ -67,6 +83,7 @@ let default = [
6783
route("docs/manual/api/dom", "./routes/ApiRoute.jsx", ~options={id: "api-dom"}),
6884
...stdlibRoutes,
6985
...beltRoutes,
86+
...domRoutes,
7087
...blogArticleRoutes,
7188
...communityRoutes,
7289
...mdxRoutes,

app/routes/DocsGuidelinesRoute.res

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
type loaderData = {
2+
compiledMdx: CompiledMdx.t,
3+
entries: array<TableOfContents.entry>,
4+
title: string,
5+
description: string,
6+
filePath: string,
7+
}
8+
9+
let loader: ReactRouter.Loader.t<loaderData> = async ({request}) => {
10+
let {pathname} = WebAPI.URL.make(~url=request.url)
11+
let filePath = MdxFile.resolveFilePath(
12+
(pathname :> string),
13+
~dir="markdown-pages/docs/guidelines",
14+
~alias="docs/guidelines",
15+
)
16+
17+
let raw = await Node.Fs.readFile(filePath, "utf-8")
18+
let {frontmatter}: MarkdownParser.result = MarkdownParser.parseSync(raw)
19+
20+
let description = switch frontmatter {
21+
| Object(dict) =>
22+
switch dict->Dict.get("description") {
23+
| Some(String(s)) => s
24+
| _ => ""
25+
}
26+
| _ => ""
27+
}
28+
29+
let title = switch frontmatter {
30+
| Object(dict) =>
31+
switch dict->Dict.get("title") {
32+
| Some(String(s)) => s
33+
| _ => ""
34+
}
35+
| _ => ""
36+
}
37+
38+
let compiledMdx = await MdxFile.compileMdx(raw, ~filePath, ~remarkPlugins=Mdx.plugins)
39+
40+
// Build table of contents entries from markdown headings
41+
let markdownTree = Mdast.fromMarkdown(raw)
42+
let tocResult = Mdast.toc(markdownTree, {maxDepth: 2})
43+
44+
let headers = Dict.make()
45+
Mdast.reduceHeaders(tocResult.map, headers)
46+
47+
let entries =
48+
headers
49+
->Dict.toArray
50+
->Array.map(((header, url)): TableOfContents.entry => {
51+
header,
52+
href: (url :> string),
53+
})
54+
->Array.slice(~start=2) // skip document entry and H1 title, keep h2 sections
55+
56+
{
57+
compiledMdx,
58+
entries,
59+
title: `${title} | ReScript Guidelines`,
60+
description,
61+
filePath,
62+
}
63+
}
64+
65+
let default = () => {
66+
let {compiledMdx, entries, title, description, filePath} = ReactRouter.useLoaderData()
67+
68+
let editHref = `https://github.com/rescript-lang/rescript-lang.org/blob/master/${filePath}`
69+
70+
let categories: array<SidebarLayout.Sidebar.Category.t> = []
71+
72+
<>
73+
<Meta title description />
74+
<NavbarSecondary />
75+
<NavbarTertiary>
76+
<a
77+
href=editHref className="inline text-14 hover:underline text-fire" rel="noopener noreferrer"
78+
>
79+
{React.string("Edit")}
80+
</a>
81+
</NavbarTertiary>
82+
<DocsLayout categories activeToc={title, entries}>
83+
<div className="markdown-body">
84+
<MdxContent compiledMdx />
85+
</div>
86+
</DocsLayout>
87+
</>
88+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
type loaderData = {
2+
compiledMdx: CompiledMdx.t,
3+
entries: array<TableOfContents.entry>,
4+
title: string,
5+
description: string,
6+
filePath: string,
7+
}
8+
9+
let loader: ReactRouter.Loader.t<loaderData>
10+
11+
let default: unit => React.element

data/api/v13.0.0/stdlib.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2427,9 +2427,9 @@
24272427
"kind": "value",
24282428
"name": "compare",
24292429
"docstrings": [
2430-
"`compare(collator, a, b)` compares two strings using the rules of `collator`. Returns a negative number when `a` comes before `b`, `0` when equal, and a positive number otherwise.\n\n## Examples\n\n```rescript\nlet collator = Intl.Collator.make(~locales=[\"en-US\"])\ncollator->Intl.Collator.compare(\"apple\", \"banana\") < 0\n```"
2430+
"`compare(collator, a, b)` compares two strings using the rules of `collator`. Returns a negative number when `a` comes before `b`, `0` when equal, and a positive number otherwise.\n\n## Examples\n\n```rescript\nlet collator = Intl.Collator.make(~locales=[\"en-US\"])\nOrdering.isLess(collator->Intl.Collator.compare(\"apple\", \"banana\"))\n```"
24312431
],
2432-
"signature": "let compare: (t, string, string) => int"
2432+
"signature": "let compare: (t, string, string) => Ordering.t"
24332433
},
24342434
{
24352435
"id": "Stdlib.Intl.Collator.ignore",
@@ -5984,9 +5984,9 @@
59845984
"kind": "value",
59855985
"name": "localeCompare",
59865986
"docstrings": [
5987-
"`localeCompare(referenceStr, compareStr)` returns a float than indicatings\nwhether a reference string comes before or after, or is the same as the given\nstring in sort order. If `referenceStr` occurs before `compareStr` positive if\nthe `referenceStr` occurs after `compareStr`, `0` if they are equivalent.\nDo not rely on exact return values of `-1` or `1`\nSee [`String.localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) on MDN.\n\n## Examples\n\n```rescript\nString.localeCompare(\"a\", \"c\") < 0.0 == true\nString.localeCompare(\"a\", \"a\") == 0.0\n```"
5987+
"`localeCompare(referenceStr, compareStr, ~locales=?, ~options=?)` returns a float indicating\nwhether a reference string comes before or after, or is the same as the given\nstring in sort order. Returns a negative value if `referenceStr` occurs before `compareStr`,\npositive if `referenceStr` occurs after `compareStr`, `0` if they are equivalent.\nDo not rely on exact return values of `-1` or `1`.\n\nOptionally takes `~locales` to specify locale(s) and `~options` to customize comparison behavior\n(e.g., sensitivity, case ordering, numeric sorting). These correspond to the `Intl.Collator` options.\nSee [`String.localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) on MDN.\n\n## Examples\n\n```rescript\nString.localeCompare(\"a\", \"c\") < 0.0 == true\nString.localeCompare(\"a\", \"a\") == 0.0\nString.localeCompare(\"a\", \"b\", ~locales=[\"en-US\"]) < 0.0 == true\nString.localeCompare(\"a\", \"A\", ~locales=[\"en-US\"], ~options={sensitivity: #base}) == 0.0\n```"
59885988
],
5989-
"signature": "let localeCompare: (string, string) => float"
5989+
"signature": "let localeCompare: (\n string,\n string,\n ~locales: array<string>=?,\n ~options: Intl_Collator.options=?,\n) => float"
59905990
},
59915991
{
59925992
"id": "Stdlib.String.ignore",

e2e/Navigation.cy.res

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
open Cy
2+
3+
// Wait for the app to fully hydrate before interacting with links.
4+
// The production build is pre-rendered so React must attach event
5+
// handlers before client-side navigation works.
6+
let waitForHydration = () => {
7+
getByTestId("navbar-primary")->shouldBeVisible->ignore
8+
cyWindow()->its("document.readyState")->shouldWithValue("eq", "complete")->ignore
9+
wait(2000)
10+
}
11+
12+
// Use short, re-queryable selectors to avoid detached DOM issues.
13+
// When React re-renders during navigation, long chains can hold
14+
// references to stale elements. Separate cy.get() calls let Cypress
15+
// re-query from the DOM root on each retry.
16+
17+
let clickNavLink = (~testId, ~text) => {
18+
get(`[data-testid="${testId}"] a:visible`)
19+
->containsChainable(text)
20+
->click
21+
->ignore
22+
}
23+
24+
let clickMobileNavLink = text => {
25+
get(`[data-testid="mobile-nav"] a:visible`)
26+
->containsChainable(text)
27+
->click
28+
->ignore
29+
}
30+
31+
let openMobileMenu = () => {
32+
get(`[data-testid="toggle-mobile-overlay"]`)->should("be.visible")->click->ignore
33+
get("#mobile-overlay")->should("be.visible")->ignore
34+
}
35+
36+
// -- Desktop (1280x720) -------------------------------------------------------
37+
38+
describe("Desktop Navigation", () => {
39+
beforeEach(() => {
40+
viewport(1280, 720)
41+
visit("/")
42+
waitForHydration()
43+
})
44+
45+
describe("Primary navbar", () => {
46+
it(
47+
"should navigate to Docs via navbar link",
48+
() => {
49+
clickNavLink(~testId="navbar-primary-left-content", ~text="Docs")
50+
url()->shouldInclude("/docs/manual/introduction")->ignore
51+
get("h1")->shouldBeVisible->ignore
52+
},
53+
)
54+
55+
it(
56+
"should navigate to Playground via navbar link",
57+
() => {
58+
clickNavLink(~testId="navbar-primary-left-content", ~text="Playground")
59+
url()->shouldInclude("/try")->ignore
60+
},
61+
)
62+
63+
it(
64+
"should navigate to Blog via navbar link",
65+
() => {
66+
clickNavLink(~testId="navbar-primary-left-content", ~text="Blog")
67+
url()->shouldInclude("/blog")->ignore
68+
},
69+
)
70+
71+
it(
72+
"should navigate to Community via navbar link",
73+
() => {
74+
clickNavLink(~testId="navbar-primary-left-content", ~text="Community")
75+
url()->shouldInclude("/community")->ignore
76+
get("h1")->shouldBeVisible->ignore
77+
},
78+
)
79+
80+
it(
81+
"should navigate home via logo after clicking away",
82+
() => {
83+
clickNavLink(~testId="navbar-primary-left-content", ~text="Blog")
84+
url()->shouldInclude("/blog")->ignore
85+
86+
get("a[aria-label='homepage']")->should("be.visible")->first->click->ignore
87+
cyLocation("pathname")->shouldWithValue("eq", "/")->ignore
88+
},
89+
)
90+
})
91+
92+
describe("Secondary navbar", () => {
93+
it(
94+
"should navigate through all secondary nav links from Docs",
95+
() => {
96+
// Click Docs in primary nav to reveal the secondary nav
97+
clickNavLink(~testId="navbar-primary-left-content", ~text="Docs")
98+
url()->shouldInclude("/docs/manual/introduction")->ignore
99+
100+
// Language Manual
101+
clickNavLink(~testId="navbar-secondary", ~text="Language Manual")
102+
url()->shouldInclude("/docs/manual/introduction")->ignore
103+
104+
// API
105+
clickNavLink(~testId="navbar-secondary", ~text="API")
106+
url()->shouldInclude("/docs/manual/api")->ignore
107+
108+
// Syntax Lookup
109+
clickNavLink(~testId="navbar-secondary", ~text="Syntax Lookup")
110+
url()->shouldInclude("/syntax-lookup")->ignore
111+
112+
// React
113+
clickNavLink(~testId="navbar-secondary", ~text="React")
114+
url()->shouldInclude("/docs/react/introduction")->ignore
115+
},
116+
)
117+
})
118+
})
119+
120+
// -- Mobile (375x667) ---------------------------------------------------------
121+
122+
describe("Mobile Navigation", () => {
123+
beforeEach(() => {
124+
viewport(375, 667)
125+
visit("/")
126+
waitForHydration()
127+
})
128+
129+
describe("Primary navbar", () => {
130+
it(
131+
"should navigate to Docs via navbar link",
132+
() => {
133+
clickNavLink(~testId="navbar-primary-left-content", ~text="Docs")
134+
url()->shouldInclude("/docs/manual/introduction")->ignore
135+
get("h1")->shouldBeVisible->ignore
136+
},
137+
)
138+
139+
it(
140+
"should navigate home via logo after clicking away",
141+
() => {
142+
clickNavLink(~testId="navbar-primary-left-content", ~text="Docs")
143+
url()->shouldInclude("/docs/manual/introduction")->ignore
144+
145+
get("a[aria-label='homepage']")->should("be.visible")->first->click->ignore
146+
cyLocation("pathname")->shouldWithValue("eq", "/")->ignore
147+
},
148+
)
149+
})
150+
151+
describe("Mobile overlay navigation", () => {
152+
it(
153+
"should navigate to Playground via mobile menu",
154+
() => {
155+
openMobileMenu()
156+
clickMobileNavLink("Playground")
157+
url()->shouldInclude("/try")->ignore
158+
},
159+
)
160+
161+
it(
162+
"should navigate to Blog via mobile menu",
163+
() => {
164+
openMobileMenu()
165+
clickMobileNavLink("Blog")
166+
url()->shouldInclude("/blog")->ignore
167+
},
168+
)
169+
170+
it(
171+
"should navigate to Community via mobile menu",
172+
() => {
173+
openMobileMenu()
174+
clickMobileNavLink("Community")
175+
url()->shouldInclude("/community")->ignore
176+
get("h1")->shouldBeVisible->ignore
177+
},
178+
)
179+
})
180+
181+
describe("Secondary navbar", () => {
182+
it(
183+
"should navigate through all secondary nav links from Docs",
184+
() => {
185+
// Click Docs in primary nav to reveal the secondary nav
186+
clickNavLink(~testId="navbar-primary-left-content", ~text="Docs")
187+
url()->shouldInclude("/docs/manual/introduction")->ignore
188+
189+
// Scroll to top so the secondary nav is visible
190+
cyScrollTo("top")
191+
192+
// Language Manual
193+
clickNavLink(~testId="navbar-secondary", ~text="Language Manual")
194+
url()->shouldInclude("/docs/manual/introduction")->ignore
195+
196+
// API
197+
cyScrollTo("top")
198+
clickNavLink(~testId="navbar-secondary", ~text="API")
199+
url()->shouldInclude("/docs/manual/api")->ignore
200+
201+
// Syntax Lookup
202+
cyScrollTo("top")
203+
clickNavLink(~testId="navbar-secondary", ~text="Syntax Lookup")
204+
url()->shouldInclude("/syntax-lookup")->ignore
205+
206+
// React
207+
cyScrollTo("top")
208+
clickNavLink(~testId="navbar-secondary", ~text="React")
209+
url()->shouldInclude("/docs/react/introduction")->ignore
210+
},
211+
)
212+
})
213+
})

0 commit comments

Comments
 (0)