Skip to content

Commit 2b5fc92

Browse files
authored
fix(react): stale lesson data after navigation (#318)
1 parent 794ae46 commit 2b5fc92

File tree

6 files changed

+67
-9
lines changed

6 files changed

+67
-9
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
type: lesson
3+
title: Layout change from
4+
template: default
5+
mainCommand: node index.mjs
6+
previews:
7+
- title: "Custom preview"
8+
port: 8000
9+
---
10+
11+
# Navigation test - Layout change from
12+
13+
This page should show previw
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
type: lesson
3+
title: Layout change to
4+
previews: false
5+
terminal:
6+
panels:
7+
- ["terminal", "Custom Terminal"]
8+
---
9+
10+
# Navigation test - Layout change to
11+
12+
This page should not show previw. It should show terminal instead.

e2e/src/content/tutorial/tests/navigation/meta.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ lessons:
55
- page-one
66
- page-two
77
- page-three
8+
- layout-change-from
9+
- layout-change-to
810
mainCommand: ''
911
prepareCommands: []
1012
---

e2e/test/navigation.test.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { test, expect } from '@playwright/test';
22

3-
const BASE_URL = '/tests/navigation/page-one';
3+
const BASE_URL = '/tests/navigation';
44

55
test('user can navigate between lessons using nav bar links', async ({ page }) => {
6-
await page.goto(BASE_URL);
6+
await page.goto(`${BASE_URL}/page-one`);
77
await expect(page.getByRole('heading', { level: 1, name: 'Navigation test - Page one' })).toBeVisible();
88

99
// navigate forwards
@@ -21,10 +21,32 @@ test('user can navigate between lessons using nav bar links', async ({ page }) =
2121
});
2222

2323
test('user can navigate between lessons using breadcrumbs', async ({ page }) => {
24-
await page.goto(BASE_URL);
24+
await page.goto(`${BASE_URL}/page-one`);
2525

2626
await page.getByRole('button', { name: 'Tests / Navigation / Page one' }).click({ force: true });
2727
await page.getByRole('region', { name: 'Navigation' }).getByRole('link', { name: 'Page three' }).click();
2828

2929
await expect(page.getByRole('heading', { level: 1, name: 'Navigation test - Page three' })).toBeVisible();
3030
});
31+
32+
test("user should see metadata's layout changes after navigation (#318)", async ({ page }) => {
33+
await page.goto(`${BASE_URL}/layout-change-from`);
34+
35+
// first page should have preview visible
36+
await expect(page.getByRole('heading', { level: 1, name: 'Navigation test - Layout change from' })).toBeVisible();
37+
await expect(page.getByText('Custom preview')).toBeVisible();
38+
39+
await page.getByRole('link', { name: 'Layout change to' }).click();
40+
await expect(page.getByRole('heading', { level: 1, name: 'Navigation test - Layout change to' })).toBeVisible();
41+
42+
// second page should have preview hidden, terminal visible
43+
44+
/* eslint-disable multiline-comment-style */
45+
// TODO: Requires #245
46+
// await expect(page.getByText('Preparing Environment')).not.toBeVisible();
47+
48+
await expect(page.getByRole('tab', { name: 'Custom Terminal', selected: true })).toBeVisible();
49+
await expect(page.getByRole('tabpanel', { name: 'Custom Terminal' })).toContainText('~/tutorial', {
50+
useInnerText: true,
51+
});
52+
});

packages/react/src/Panels/WorkspacePanel.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ interface TerminalProps extends PanelProps {
3232
* This component is the orchestrator between various interactive components.
3333
*/
3434
export function WorkspacePanel({ tutorialStore, theme }: Props) {
35+
/**
36+
* Re-render when lesson changes.
37+
* The `tutorialStore.hasEditor()` and other methods below access
38+
* stale data as they are not reactive.
39+
*/
40+
useStore(tutorialStore.ref);
41+
3542
const hasEditor = tutorialStore.hasEditor();
3643
const hasPreviews = tutorialStore.hasPreviews();
3744
const hideTerminalPanel = !tutorialStore.hasTerminalPanel();
@@ -89,6 +96,7 @@ function EditorSection({ theme, tutorialStore, hasEditor }: PanelProps) {
8996
const selectedFile = useStore(tutorialStore.selectedFile);
9097
const currentDocument = useStore(tutorialStore.currentDocument);
9198
const lessonFullyLoaded = useStore(tutorialStore.lessonFullyLoaded);
99+
const storeRef = useStore(tutorialStore.ref);
92100

93101
const lesson = tutorialStore.lesson!;
94102

@@ -116,7 +124,7 @@ function EditorSection({ theme, tutorialStore, hasEditor }: PanelProps) {
116124
} else {
117125
setHelpAction('reset');
118126
}
119-
}, [tutorialStore.ref]);
127+
}, [storeRef]);
120128

121129
return (
122130
<Panel
@@ -128,7 +136,7 @@ function EditorSection({ theme, tutorialStore, hasEditor }: PanelProps) {
128136
className="transition-theme bg-tk-elements-panel-backgroundColor text-tk-elements-panel-textColor"
129137
>
130138
<EditorPanel
131-
id={tutorialStore.ref}
139+
id={storeRef}
132140
theme={theme}
133141
showFileTree={tutorialStore.hasFileTree()}
134142
editorDocument={currentDocument}
@@ -157,6 +165,7 @@ function PreviewsSection({
157165
const previewRef = useRef<ImperativePreviewHandle>(null);
158166
const lesson = tutorialStore.lesson!;
159167
const terminalConfig = useStore(tutorialStore.terminalConfig);
168+
const storeRef = useStore(tutorialStore.ref);
160169

161170
function showTerminal() {
162171
const { current: terminal } = terminalPanelRef;
@@ -206,7 +215,7 @@ function PreviewsSection({
206215
});
207216

208217
return () => unsubscribe();
209-
}, [tutorialStore.ref]);
218+
}, [storeRef]);
210219

211220
return (
212221
<Panel

packages/runtime/src/store/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class TutorialStore {
3939
private _lessonFilesFetcher: LessonFilesFetcher;
4040
private _lessonTask: Task<unknown> | undefined;
4141
private _lesson: Lesson | undefined;
42-
private _ref: number = 1;
42+
private _ref = atom(1);
4343
private _themeRef = atom(1);
4444

4545
private _lessonFiles: Files | undefined;
@@ -135,7 +135,7 @@ export class TutorialStore {
135135

136136
this._lessonTask?.cancel();
137137

138-
this._ref += 1;
138+
this._ref.set(1 + (this._ref.value || 0));
139139
this._lesson = lesson;
140140
this.lessonFullyLoaded.set(false);
141141

@@ -219,7 +219,7 @@ export class TutorialStore {
219219
return this._lesson;
220220
}
221221

222-
get ref(): unknown {
222+
get ref(): ReadableAtom<unknown> {
223223
return this._ref;
224224
}
225225

0 commit comments

Comments
 (0)