Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions _plugins/search_index_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ def publish_flat_index(lang_code, menu)
end
end
end
# Learning paths (spec 006): searchable by their title, summary and step titles.
label = lang_code == 'pt-BR' ? 'Trilhas de aprendizado' : 'Learning paths'
(@site.data.dig(lang_code, 'learning-paths') || {}).sort.each do |slug, path|
next unless path.is_a?(Hash)

steps = Array(path['stages']).flat_map { |stage| Array(stage['steps']).map { |step| step['title'] } }.compact
entries << { 'title' => path['title'], 'url' => "/#{lang_code}/learning-paths/#{slug}/", 'type' => 'learning-path',
'category' => label, 'description' => [path['summary'], steps.join(' 路 ')].compact.join(' ') }
end
topics = topic_lookup(lang_code, menu)
seen = {}
each_real_resource(@site.data[lang_code], [], [], &lambda { |resource, trail, keys|
Expand Down
4 changes: 2 additions & 2 deletions docs/specs/006-learning-paths/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
- [x] T1 Data schema and test (`test/learning_paths_test.rb`)
- [x] T2 Generator, layout and index page (en-US, pt-BR)
- [x] T3 Interaction script: details, progress, live region; no-JS fallback
- [ ] T4 Subcategory pages list their paths (done); search indexes paths (pending)
- [x] T4 Subcategory pages list their paths; search indexes paths
- [x] T5 Author 12 paths per language from IPL data (frontend, backend, Python, JavaScript, data analysis, machine
learning, DevOps, cybersecurity, databases, UX, mobile, computer science), with reviews
- [ ] T6 e2e and axe checks
- [x] T6 e2e and axe checks
2 changes: 2 additions & 0 deletions e2e/tests/accessibility.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const PAGES = [
['section', '/en-US/academics/computer-science-theory/cryptography-theory/'],
['wiki', '/en-US/academics/computer-science-theory/cryptography-theory/wiki/'],
['search', '/en-US/search/'],
['learning paths', '/en-US/learning-paths/'],
['learning path pt-BR', '/pt-BR/learning-paths/frontend/'],
['not found', '/this-page-does-not-exist/'],
];

Expand Down
38 changes: 38 additions & 0 deletions e2e/tests/learning-paths.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// @ts-check
import { test, expect } from '@playwright/test';

// Spec 006: progress per step is kept in the reader's browser and announced; steps work as <details>.
test('learning path progress persists across reloads and is announced', async ({ page }) => {
await page.goto('/en-US/learning-paths/frontend/');
const steps = page.locator('.learning-path-step');
const total = await steps.count();
expect(total).toBeGreaterThan(5);

const bar = page.getByRole('progressbar');
await expect(bar).toHaveAttribute('aria-valuenow', '0');

// Only the next unfinished step is open with JavaScript.
await expect(page.locator('.learning-path-step details[open]')).toHaveCount(1);

const first = steps.first();
await first.getByRole('checkbox', { name: 'I finished this step' }).check();
await expect(bar).toHaveAttribute('aria-valuenow', '1');
await expect(page.getByRole('status')).toContainText(`1 / ${total}`);

await page.reload();
await expect(page.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '1');
// A finished step is collapsed, so read its checkbox directly.
await expect(page.locator('.learning-path-step').first().locator('input[type="checkbox"]')).toBeChecked();
// After a reload the second step is the next one to open.
await expect(page.locator('.learning-path-step').nth(1).locator('details')).toHaveAttribute('open', '');
});

test('learning path is readable without JavaScript', async ({ browser }) => {
const context = await browser.newContext({ javaScriptEnabled: false });
const page = await context.newPage();
await page.goto('/pt-BR/learning-paths/python/');
const open = page.locator('.learning-path-step details[open]');
expect(await open.count()).toBe(await page.locator('.learning-path-step').count());
await expect(page.locator('.learning-path-step a[href^="https://"]').first()).toBeVisible();
await context.close();
});
10 changes: 10 additions & 0 deletions test/build_output_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ def test_subcategory_pages_list_their_curated_resources
assert_operator html.scan(/rel="noopener" class="font-semibold text-blue-700/).size, :>=, 10
end

# Spec 006 T4: learning paths are searchable, and their "see also" links are not indexed as resources.
def test_search_index_lists_learning_paths
entries = JSON.parse(SiteBuild.output('api/pt-BR/search-index.json'))
paths = entries.select { |entry| entry['type'] == 'learning-path' }

assert_operator paths.size, :>=, 12
assert(paths.any? { |entry| entry['url'] == '/pt-BR/learning-paths/python/' })
refute(entries.any? { |entry| entry['type'] == 'resource' && entry['url'].start_with?('https://roadmap.sh/') && entry['category'].to_s.include?('See also') })
end

def test_library_totals_count_every_curated_resource
stats = SiteBuild.site.data['resource_stats']
html = SiteBuild.output('en-US/categories/index.html')
Expand Down
Loading