Skip to content

Commit 5630f6b

Browse files
committed
fix(astro): sorting of mixed hierarchy parts
1 parent e65eee1 commit 5630f6b

File tree

9 files changed

+113
-7
lines changed

9 files changed

+113
-7
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
type: lesson
3+
title: Lesson three
4+
---
5+
6+
# Lessons in part test - Lesson three
7+
8+
Lesson in chapter
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
type: lesson
3+
title: Lesson four
4+
---
5+
6+
# Lessons in part test - Lesson four
7+
8+
Lesson in chapter
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
type: chapter
3+
title: 'Chapter one'
4+
---
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
type: part
3+
title: 'Part two'
4+
---

e2e/test/navigation.lessons-in-part.test.ts

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

33
test('user can navigate between lessons using breadcrumbs', async ({ page }) => {
4-
await page.goto('/part-one/lesson-one');
4+
await page.goto('/');
55

66
await expect(page.getByRole('heading', { level: 1, name: 'Lessons in part test - Lesson one' })).toBeVisible();
77
await expect(page.getByText('Lesson in part without chapter')).toBeVisible();
@@ -13,7 +13,50 @@ test('user can navigate between lessons using breadcrumbs', async ({ page }) =>
1313
await expect(page.locator('[data-state="open"]', { has: button })).toBeVisible({ timeout: 50 });
1414
}).toPass();
1515

16-
await page.getByRole('navigation').getByRole('link', { name: 'Lesson two' }).click();
16+
const navigation = page.getByRole('navigation');
17+
await navigation.getByRole('region', { name: 'Part 1: Part one' }).getByRole('link', { name: 'Lesson two' }).click();
1718

1819
await expect(page.getByRole('heading', { level: 1, name: 'Lessons in part test - Lesson two' })).toBeVisible();
20+
await expect(page.getByText('Lesson in part without chapter')).toBeVisible();
21+
22+
await expect(async () => {
23+
const button = page.getByRole('button', { name: 'Part one / Lesson two' });
24+
await button.click();
25+
await expect(page.locator('[data-state="open"]', { has: button })).toBeVisible({ timeout: 50 });
26+
}).toPass();
27+
28+
// expand part
29+
await navigation.getByRole('button', { name: 'Part 2: Part two' }).click();
30+
31+
// expand chapter
32+
await navigation
33+
.getByRole('region', { name: 'Part 2: Part two' })
34+
.getByRole('button', { name: 'Chapter one' })
35+
.click();
36+
37+
// select lesson
38+
await navigation.getByRole('region', { name: 'Chapter one' }).getByRole('link', { name: 'Lesson three' }).click();
39+
40+
await expect(page.getByRole('heading', { level: 1, name: 'Lessons in part test - Lesson three' })).toBeVisible();
41+
await expect(page.getByText('Lesson in chapter')).toBeVisible();
42+
});
43+
44+
test('user can navigate between lessons using nav bar links', async ({ page }) => {
45+
await page.goto('/');
46+
await expect(page.getByRole('heading', { level: 1, name: 'Lessons in part test - Lesson one' })).toBeVisible();
47+
await expect(page.getByText('Lesson in part without chapter')).toBeVisible();
48+
49+
await navigateToPage('Lesson two');
50+
await expect(page.getByText('Lesson in part without chapter')).toBeVisible();
51+
52+
await navigateToPage('Lesson three');
53+
await expect(page.getByText('Lesson in chapter')).toBeVisible();
54+
55+
await navigateToPage('Lesson four');
56+
await expect(page.getByText('Lesson in chapter')).toBeVisible();
57+
58+
async function navigateToPage(title: string) {
59+
await page.getByRole('link', { name: title }).click();
60+
await expect(page.getByRole('heading', { level: 1, name: `Lessons in part test - ${title}` })).toBeVisible();
61+
}
1962
});

packages/astro/src/default/utils/content.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,41 @@ test('lessons with identical names in different chapters', async () => {
136136
expect(lessons[1].part?.id).toBe('1-part');
137137
});
138138

139+
test('lessons with identical names in mixed hierarchy', async () => {
140+
getCollection.mockReturnValueOnce([
141+
{ id: 'meta.md', ...tutorial },
142+
{ id: '1-part/meta.md', ...part },
143+
{ id: '2-part/meta.md', ...part },
144+
{ id: '2-part/2-chapter/meta.md', ...chapter },
145+
146+
{ id: '1-part/identical-lesson-name/content.md', ...lesson },
147+
{ id: '1-part/2nd-identical-lesson-name/content.md', ...lesson },
148+
149+
{ id: '2-part/2-chapter/identical-lesson-name/content.md', ...lesson },
150+
{ id: '2-part/2-chapter/2nd-identical-lesson-name/content.md', ...lesson },
151+
]);
152+
153+
const collection = await getTutorial();
154+
const lessons = collection.lessons;
155+
156+
// verify that lesson.id is not used to define what makes a lesson unique (part.id + chapter.id too)
157+
expect(lessons).toHaveLength(4);
158+
expect(lessons[0].id).toBe('identical-lesson-name');
159+
expect(lessons[1].id).toBe('2nd-identical-lesson-name');
160+
expect(lessons[2].id).toBe('identical-lesson-name');
161+
expect(lessons[3].id).toBe('2nd-identical-lesson-name');
162+
163+
expect(lessons[0].chapter?.id).toBe(undefined);
164+
expect(lessons[1].chapter?.id).toBe(undefined);
165+
expect(lessons[2].chapter?.id).toBe('2-chapter');
166+
expect(lessons[3].chapter?.id).toBe('2-chapter');
167+
168+
expect(lessons[0].part?.id).toBe('1-part');
169+
expect(lessons[1].part?.id).toBe('1-part');
170+
expect(lessons[2].part?.id).toBe('2-part');
171+
expect(lessons[3].part?.id).toBe('2-part');
172+
});
173+
139174
test('single part and lesson, no chapter', async (ctx) => {
140175
getCollection.mockReturnValueOnce([
141176
{ id: 'meta.md', ...tutorial },

packages/astro/src/default/utils/content.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -366,14 +366,16 @@ function assertTutorialStructure(tutorial: Tutorial) {
366366
}
367367

368368
function sortTutorialLessons(tutorial: Tutorial, metadata: TutorialSchema) {
369-
const lessonOrder: Lesson['id'][] = [];
370369
const lessonIds = tutorial.lessons.map((lesson) => lesson.id);
371370

371+
// lesson ID alone does not make a lesson unique - combination of lessonId + chapterId + partId does
372+
const lessonOrder: { lessonId: Lesson['id']; chapterId?: Chapter['id']; partId?: Part['id'] }[] = [];
373+
372374
const lessonsInRoot = Object.keys(tutorial.parts).length === 0;
373375

374376
// if lessons in root, sort by tutorial.lessons and metadata.lessons
375377
if (lessonsInRoot) {
376-
lessonOrder.push(...getOrder(metadata.lessons, lessonIds));
378+
lessonOrder.push(...getOrder(metadata.lessons, lessonIds).map((lessonId) => ({ lessonId })));
377379
}
378380

379381
// if no lessons in root, sort by parts and their possible chapters
@@ -393,7 +395,7 @@ function sortTutorialLessons(tutorial: Tutorial, metadata: TutorialSchema) {
393395

394396
// all lessons are in part, no chapters
395397
if (partLessons.length) {
396-
lessonOrder.push(...getOrder(part.data.lessons, partLessons));
398+
lessonOrder.push(...getOrder(part.data.lessons, partLessons).map((lessonId) => ({ lessonId, partId })));
397399
continue;
398400
}
399401

@@ -413,14 +415,16 @@ function sortTutorialLessons(tutorial: Tutorial, metadata: TutorialSchema) {
413415

414416
const chapterLessonOrder = getOrder(chapter.data.lessons, chapterLessons);
415417

416-
lessonOrder.push(...chapterLessonOrder);
418+
lessonOrder.push(...chapterLessonOrder.map((lessonId) => ({ lessonId, partId, chapterId })));
417419
}
418420
}
419421
}
420422

421423
// finally apply overall order for lessons
422424
for (const lesson of tutorial.lessons) {
423-
lesson.order = lessonOrder.indexOf(lesson.id);
425+
lesson.order = lessonOrder.findIndex(
426+
(l) => l.lessonId === lesson.id && l.chapterId === lesson.chapter?.id && l.partId === lesson.part?.id,
427+
);
424428
}
425429

426430
tutorial.lessons.sort((a, b) => a.order - b.order);

0 commit comments

Comments
 (0)