Skip to content

Feat/graphql challenge map readme/tests update#578

Open
CarlyAThomas wants to merge 9 commits into
freeCodeCamp:mainfrom
CarlyAThomas:feat/graphql-challenge-map-readme/tests-update
Open

Feat/graphql challenge map readme/tests update#578
CarlyAThomas wants to merge 9 commits into
freeCodeCamp:mainfrom
CarlyAThomas:feat/graphql-challenge-map-readme/tests-update

Conversation

@CarlyAThomas

@CarlyAThomas CarlyAThomas commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

Checklist:

Summary

This PR extends the work merged in #574 by:

  • Adding README documentation for generating challengeMap.json and recommending a weekly refresh.
  • Updating challenge map tests to run against the real challengeMap.json (when present) to validate that FCC Proper output is digestible by Classroom.
  • Keeping synthetic tests to validate core transformation logic independent of real data.
  • Ensuring the real-map tests fail with a clear message when the map is missing.

Co-authored-by: Newton Chung <NewtonLC@users.noreply.github.com>

Why keep both synthetic and real-map tests?

Synthetic tests validate the transformation logic in isolation (stable, deterministic).
Real-map tests validate that the actual FCC Proper map format is compatible with Classroom. This is important since the upstream map can change over time.

This aligns with the feedback:

“Tests should run against the actual challengeMap.json from FCC Proper … If the file does not exist, the test should fail and tell the user to generate it.”

New real-map test descriptions

  1. loads a non-empty challenge map
    Confirms challengeMap.json exists, parses, and has entries. Prevents downstream logic from running on empty or missing data.

  2. builds dashboard data using the first valid map entry
    Verifies the map’s real structure can be transformed into the expected nested dashboard shape.

  3. skips unknown challenge IDs
    Ensures the logic ignores unknown/missing IDs safely (no crashes or mis-grouping).

  4. resolves multiple students against the current map
    Validates per-student transformation with real map data and the expected output shape.

Fail example (missing map):

npm run test:challenge-map

> @freecodecamp/classroom@0.0.0 test:challenge-map
> jest __tests__/utils/challengeMapUtils.test.js

info  - Loaded env from ../classroom/.env
  console.log
    [challengeMapUtils.test] Missing challenge map
      Missing challenge map path: ../classroom/data/challengeMap.json
      Current working directory: ../classroom
      Resolved map path: ../classroom/data/challengeMap.json
      To generate the challengeMap.json please run:
          node scripts/build-challenge-map-graphql.mjs
    
      Tests that rely on the challenge map will fail until the map is generated.

      at Object.log (__tests__/utils/challengeMapUtils.test.js:19:11)

 FAIL  __tests__/utils/challengeMapUtils.test.js
  challengeMapUtils (real challengeMap.json)
    ✕ challengeMap.json must exist to run real-map tests (1 ms)
  challengeMapUtils (synthetic map)
    buildStudentDashboardData
      ✓ should transform flat challenge array into nested certification structure (1 ms)
      ✓ should use first superblock as canonical certification
      ✓ should use first block as canonical block (1 ms)
      ✓ should nest challenges under correct certification and block
      ✓ should include challengeName from map
      ✓ should skip unknown challenge IDs
      ✓ should handle empty challenge array
      ✓ should handle multiple challenges in different certifications
      ✓ should preserve original challenge data properties
      ✓ should include challenges that are only in legacy certifications (-22) (1 ms)
      ✓ should use first superblock even if legacy when both versions exist
    resolveAllStudentsToDashboardFormat
      ✓ should transform multiple students data
      ✓ should include certifications for each student (1 ms)
      ✓ should handle empty student data object
      ✓ should handle null student data
      ✓ should handle undefined student data
      ✓ should handle non-object student data (1 ms)
      ✓ should use provided curriculumMap parameter
      ✓ should process students independently
      ✓ should handle student with no completed challenges (1 ms)
      ✓ should preserve email case sensitivity
    integration scenarios
      ✓ should handle real-world scenario with multiple students and certifications
      ✓ should track challenges from multiple superblocks correctly (1 ms)

  ● challengeMapUtils (real challengeMap.json) › challengeMap.json must exist to run real-map tests

    Missing data/challengeMap.json. Run: node scripts/build-challenge-map-graphql.mjs

      121 |     test('challengeMap.json must exist to run real-map tests', () => {
      122 |       expect(true).toBe(true);
    > 123 |       throw new Error(
          |             ^
      124 |         'Missing data/challengeMap.json. Run: node scripts/build-challenge-map-graphql.mjs'
      125 |       );
      126 |     });

      at Object.<anonymous> (__tests__/utils/challengeMapUtils.test.js:123:13)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 23 passed, 24 total
Snapshots:   0 total
Time:        0.292 s, estimated 1 s
Ran all test suites matching /__tests__\/utils\/challengeMapUtils.test.js/i.

Pass example (map generated):

npm run test:challenge-map

> @freecodecamp/classroom@0.0.0 test:challenge-map
> jest __tests__/utils/challengeMapUtils.test.js

info  - Loaded env from ../classroom/.env
  console.log
    [challengeMapUtils.test] Using challenge map: ../classroom/data/challengeMap.json

      at Object.log (__tests__/utils/challengeMapUtils.test.js:109:11)

  console.log
    [challengeMapUtils.test] Challenge map entries: 12847

      at Object.log (__tests__/utils/challengeMapUtils.test.js:115:11)

 PASS  __tests__/utils/challengeMapUtils.test.js
  challengeMapUtils (real challengeMap.json)
    ✓ loads a non-empty challenge map (2 ms)
    ✓ builds dashboard data using the first valid map entry (3 ms)
    ✓ skips unknown challenge IDs
    ✓ resolves multiple students against the current map (7 ms)
  challengeMapUtils (synthetic map)
    buildStudentDashboardData
      ✓ should transform flat challenge array into nested certification structure (1 ms)
      ✓ should use first superblock as canonical certification
      ✓ should use first block as canonical block
      ✓ should nest challenges under correct certification and block
      ✓ should include challengeName from map
      ✓ should skip unknown challenge IDs
      ✓ should handle empty challenge array (1 ms)
      ✓ should handle multiple challenges in different certifications
      ✓ should preserve original challenge data properties
      ✓ should include challenges that are only in legacy certifications (-22)
      ✓ should use first superblock even if legacy when both versions exist (1 ms)
    resolveAllStudentsToDashboardFormat
      ✓ should transform multiple students data
      ✓ should include certifications for each student
      ✓ should handle empty student data object
      ✓ should handle null student data (1 ms)
      ✓ should handle undefined student data
      ✓ should handle non-object student data
      ✓ should use provided curriculumMap parameter
      ✓ should process students independently
      ✓ should handle student with no completed challenges
      ✓ should preserve email case sensitivity
    integration scenarios
      ✓ should handle real-world scenario with multiple students and certifications (1 ms)
      ✓ should track challenges from multiple superblocks correctly

Test Suites: 1 passed, 1 total
Tests:       27 passed, 27 total
Snapshots:   0 total
Time:        0.519 s, estimated 1 s
Ran all test suites matching /__tests__\/utils\/challengeMapUtils.test.js/i.

Testing:

npm run test:challenge-map

Testing / CI Note

Local dev: If challengeMap.json is missing, the real‑map suite fails with a clear error and guidance to run node scripts/build-challenge-map-graphql.mjs.
CI: When CI=true and the map is missing, the real‑map suite is skipped to keep the pipeline green. Synthetic tests still run.

@CarlyAThomas
CarlyAThomas requested a review from a team as a code owner February 17, 2026 02:25

@NewtonLC NewtonLC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this out in a Windows environment and originally, upon running node scripts/build-challenge-map-graphql.mjs, there was an issue with the pathing of the challenge map. I updated the file path to use the fileURLToPath() function and it should now work on all machines!

Aside from that, this PR looks ready for review.

Comment thread README.md
8. Run `npm run mock-fcc-data`
9. Run `npx prisma studio`

### Challenge map (FCC Proper)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some explanation about the purpose of the Challenge Map? Why does does Classroom App need it? How is it consumed/transformed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added additional notation in the readme about the purpose of the Challenge Map and how it works so that the functionality is not lost going forward as well as specifics in the file itself and the Utils file explaining how the breakdown works so that if anyone addresses the functions in the future they have context.

Comment thread util/challengeMapUtils.js Outdated
const name = mapEntry.name;
const certification =
mapEntry.certification || (mapEntry.superblocks || [])[0];
const block = mapEntry.block || (mapEntry.blocks || [])[0];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to check for both cases mapEntry.certification AND mapEntry.superblocks? Are we not sure what the actual property name is? Is the property name sometimes "certification" and at other times "superblocks"? Same question for "block" vs. "blocks" -- is it sometimes one vs. the other?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short answer: that code was dead and has been removed. It was a transition artifact — the build script originally wrote singular fields (certification, block), then was updated to write arrays (superblocks, blocks). The conditional mapEntry.certification || (mapEntry.superblocks || [])[0] was the bridge during that migration and was never cleaned up. Now that the build script always emits arrays, we replaced the whole inline expression with getCanonicalChallengeMapLocation(mapEntry) from challengeMapHelpers.js, which only reads from the arrays. I removed the additional code as it is no longer needed and it makes the code much simpler and straightforward.

Comment on lines +96 to +97
mapEntry.certification || (mapEntry.superblocks || [])[0];
const block = mapEntry.block || (mapEntry.blocks || [])[0];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2nd time this code is duplicated. Can we prevent the code duplication?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a util/challengeMapHelpers.js file to consolidate the duplicated logic. This now uses the util/challengeMapHelpers.js file.

Comment on lines +48 to +49
mapEntry.certification || (mapEntry.superblocks || [])[0];
const block = mapEntry.block || (mapEntry.blocks || [])[0];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code gets duplicated below. Is there a way to prevent the duplication of this code?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now uses the util/challengeMapHelpers.js file.

CarlyAThomas and others added 9 commits July 20, 2026 18:33
- Add scripts/build-challenge-map-graphql.mjs to generate challengeMap.json
  * Fetches curriculum from FCC GraphQL API
  * Builds flat map with { certification, block, name } structure
  * Output: data/challengeMap.json with 12,847 unique challenges
  * Run: node scripts/build-challenge-map-graphql.mjs

- Add util/challengeMapUtils.js for transforming student data
  * resolveAllStudentsToDashboardFormat() - converts FCC Proper student data to dashboard format
  * buildStudentDashboardData() - groups challenges by certification and block

- Update .gitignore to exclude generated data/challengeMap.json
… and tests

- Extract getCanonicalChallengeMapLocation into util/challengeMapHelpers.js so
  the "first element wins" canonical rule lives in one place
- Remove dead mapEntry.certification fallback from challengeMapUtils.js (was a
  schema-migration artifact; build script only emits arrays now)
- Lazy-load challengeMap.json to fix Babel/CJS __dirname collision under Jest
- Expand README Challenge Map section: purpose, why-canonical design reasoning,
  generate/test commands
- Refactor tests: remove jest.mock(fs/path) and duplicate implementations;
  import real functions; add real-map suite that reads data/challengeMap.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@CarlyAThomas
CarlyAThomas force-pushed the feat/graphql-challenge-map-readme/tests-update branch from 8dca44a to 0f4bbca Compare July 21, 2026 01:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants