Skip to content

Convert hints editor unit tests to Vue Testing Library#5842

Open
Swoyamjeetcodes wants to merge 35 commits intolearningequality:unstablefrom
Swoyamjeetcodes:chore/5789-hintseditor-vtl
Open

Convert hints editor unit tests to Vue Testing Library#5842
Swoyamjeetcodes wants to merge 35 commits intolearningequality:unstablefrom
Swoyamjeetcodes:chore/5789-hintseditor-vtl

Conversation

@Swoyamjeetcodes
Copy link
Copy Markdown
Contributor

@Swoyamjeetcodes Swoyamjeetcodes commented Apr 17, 2026

Fixes #5815

Summary

Refactored channelEdit/components/HintsEditor/HintsEditor.spec.js to Vue Testing Library and rewrote the suite to reflect user interactions.

What changed

  • Migrated from @vue/test-utils to:
    • @testing-library/vue (render, screen, within)
    • @testing-library/user-event
  • Added a reusable renderComponent helper (with router + component stubs).
  • Added a smoke test.
  • Reworked test coverage to user-facing workflows:
    • empty state when no hints exist
    • rendering hint order
    • editing hint text
    • adding a new hint
    • opening another hint
    • moving hints up/down (including open index behavior)
    • deleting hints (including close/open behavior)
  • Removed residual VTU usage/imports from the migrated file.

Manual verification

  • Ran:
    • pnpm test contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.spec.js
  • Result:
    • Test Suites: 1 passed
    • Tests: 12 passed

UI evidence

  • Screen recording (Hints workflow in Questions tab):

Screen recording (Hints workflow in Questions tab)

Before After
View recording View recording

Passed test cases

testcases.mp4

References

Reviewer guidance

  • Review only this file:
    • contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.spec.js
  • Run:
    • pnpm test contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.spec.js
  • Please sanity-check:
    • User-focused test descriptions
    • Use of Testing Library query patterns
    • Open-index behavior around move/delete flows

AI usage

I used Codex (GPT-5) to help migrate the test suite and draft this PR description.
I critically reviewed and edited the generated output to match project testing conventions, removed unnecessary/legacy VTU patterns, and verified correctness by rerunning the migrated Jest file until all tests passed.

@learning-equality-bot
Copy link
Copy Markdown

👋 Hi @Swoyamjeetcodes, thanks for contributing!

For the review process to begin, please verify that the following is satisfied:

  • Contribution is aligned with our contributing guidelines

  • Pull request description has correctly filled AI usage section & follows our AI guidance:

    AI guidance

    State explicitly whether you didn't use or used AI & how.

    If you used it, ensure that the PR is aligned with Using AI as well as our DEEP framework. DEEP asks you:

    • Disclose — Be open about when you've used AI for support.
    • Engage critically — Question what is generated. Review code for correctness and unnecessary complexity.
    • Edit — Review and refine AI output. Remove unnecessary code and verify it still works after your edits.
    • Process sharing — Explain how you used the AI so others can learn.

    Examples of good disclosures:

    "I used Claude Code to implement the component, prompting it to follow the pattern in ComponentX. I reviewed the generated code, removed unnecessary error handling, and verified the tests pass."

    "I brainstormed the approach with Gemini, then had it write failing tests for the feature. After reviewing the tests, I used Claude Code to generate the implementation. I refactored the output to reduce verbosity and ran the full test suite."

Also check that issue requirements are satisfied & you ran pre-commit locally.

Pull requests that don't follow the guidelines will be closed.

Reviewer assignment can take up to 2 weeks.

@learning-equality-bot
Copy link
Copy Markdown

📢✨ Before we assign a reviewer, we'll turn on @rtibblesbot to pre-review. Its comments are generated by an LLM, and should be evaluated accordingly.

Copy link
Copy Markdown
Contributor

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

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

Clean migration — all acceptance criteria from #5815 met.

CI passing. Phase 3 skipped (no UI files changed). Two of the three PR videos (before/after recordings) failed to download — their S3 pre-signed URLs appear to have expired; only the test results video is accessible.

  • praise: see inline — thorough open-index tracking coverage
  • nitpick: see inline — toBeDefined() in clickToolbarAction

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Reviewed the pull request diff checking for:

  • Correctness: bugs, edge cases, undocumented behavior, resource leaks, hardcoded values
  • Design: unnecessary complexity, naming, readability, comment accuracy, redundant state
  • Architecture: duplicated concerns, minimal interfaces, composition over inheritance
  • Testing: behavior-based assertions, mocks only at hard boundaries, accurate coverage
  • Completeness: missing dependencies, unupdated usages, i18n, accessibility, security
  • Principles: DRY (same reason to change), SRP, Rule of Three (no premature abstraction)
  • Checked CI status and linked issue acceptance criteria
  • For UI changes: inspected screenshots for layout, visual completeness, and consistency

.trigger('click');
const clickToolbarAction = async ({ action, hintIdx, user }) => {
const buttons = screen.getAllByTestId(`toolbarIcon-${action}`);
expect(buttons[hintIdx]).toBeDefined();
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.

nitpick: toBeDefined() is a plain Jest matcher. Since buttons[hintIdx] is a DOM node, toBeInTheDocument() is more idiomatic Testing Library and produces a clearer failure message if the button isn't found at the expected index.

@learning-equality-bot
Copy link
Copy Markdown

📢✨ Before we assign a reviewer, we'll invite community pre-review. See the community review guidance for both authors and reviewers.

@MisRob MisRob changed the title fixes #5815 Convert hints editor unit tests to Vue Testing Library Apr 17, 2026
Copy link
Copy Markdown
Member

@AlexVelezLl AlexVelezLl left a comment

Choose a reason for hiding this comment

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

Thanks a lot for your contribution @Swoyamjeetcodes! Just a couple of things we may want to change to better align with new testing conventions in the org. Thanks!

Comment on lines +7 to +9
configure({
testIdAttribute: 'data-test',
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@MisRob, I know we agreed on using data-testid for this, but I see that if we want to change this here, this will create a domino effect because there are tests relying on components like AssessmentItemToolbar that are also tested on other test suites, and we may end up modifying too many files for this. Is it okay if we leave it like this for now, and then, when all these related components are migrated, make the change to data-testid?

Comment on lines +65 to +86
expect(screen.getByRole('button', { name: 'New hint' })).toBeInTheDocument();
});

it('renders a placeholder when there are no hints', () => {
wrapper = mount(HintsEditor, {
propsData: {
hints: [],
},
it('shows an empty-state message when a question has no hints', () => {
renderComponent({
hints: [],
});

expect(wrapper.html()).toContain('Question has no hints');
expect(screen.getByText('Question has no hints')).toBeInTheDocument();
});

it('renders all hints in a correct order', () => {
wrapper = mount(HintsEditor, {
propsData: {
hints: [
{ hint: 'First hint', order: 1 },
{ hint: 'Second hint', order: 2 },
],
},
it('shows hints in the same order as the question', () => {
renderComponent({
hints: [
{ hint: 'First hint', order: 1 },
{ hint: 'Second hint', order: 2 },
],
});

// Find all instances of your new RichTextEditor component
const editors = wrapper.findAllComponents({ name: 'RichTextEditor' });
expect(editors.length).toBe(2);

// Instead of checking the raw HTML, we check the `value` prop passed to each editor.
expect(editors.at(0).props('value')).toBe('First hint');
expect(editors.at(1).props('value')).toBe('Second hint');
const hintCards = getHintCards();
expect(within(hintCards[0]).getByText('First hint')).toBeInTheDocument();
expect(within(hintCards[1]).getByText('Second hint')).toBeInTheDocument();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we use the translation keys instead of the hardcoded strings for labels, please? So, instead of

screen.getByRole('button', { name: 'New hint' })

We could do

screen.getByRole('button', { name: HintsEditor.$trs.newHintBtnLabel })

Comment on lines +11 to +35
const MockTipTapEditor = {
name: 'TipTapEditor',
props: {
value: {
type: String,
default: '',
},
mode: {
type: String,
default: 'view',
},
},
template: `
<div>
<p v-if="value">{{ value }}</p>
<button
v-if="mode === 'edit'"
type="button"
aria-label="Update hint text"
@click="$emit('update', 'Updated hint')"
>
Update hint text
</button>
</div>
`,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I imagine this would be useful for other tests, too. Could you please create the mock on contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/__mocks__/TipTapEditor.vue and then just mock it with jest.mock('shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue');

For this mock's implementation, let's do it in a way that's closer to the actual implementation, like having this expose only a textarea element that emits the update event on change. Then we can just query this textarea, and use user.type to get the update events :). This way, we don't need to create elements like this button that may be confusing.

@akolson
Copy link
Copy Markdown
Member

akolson commented May 4, 2026

Hi @Swoyamjeetcodes! Could you please take a look at the feedback provided by @AlexVelezLl? Also, do let us know if you are unable to. Thank you!

dependabot Bot and others added 18 commits May 7, 2026 22:59
Bumps [celery](https://github.com/celery/celery) from 5.6.0 to 5.6.3.
- [Release notes](https://github.com/celery/celery/releases)
- [Changelog](https://github.com/celery/celery/blob/v5.6.3/Changelog.rst)
- [Commits](celery/celery@v5.6.0...v5.6.3)

---
updated-dependencies:
- dependency-name: celery
  dependency-version: 5.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](docker/setup-qemu-action@v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps the babel group with 1 update: [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env).


Updates `@babel/preset-env` from 7.29.0 to 7.29.2
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.29.2/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-version: 7.29.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: babel
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [axios](https://github.com/axios/axios) from 1.13.5 to 1.15.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](axios/axios@v1.13.5...v1.15.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.15.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [workbox-precaching](https://github.com/googlechrome/workbox) from 7.3.0 to 7.4.0.
- [Release notes](https://github.com/googlechrome/workbox/releases)
- [Commits](GoogleChrome/workbox@v7.3.0...v7.4.0)

---
updated-dependencies:
- dependency-name: workbox-precaching
  dependency-version: 7.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
…earningequality#5841)

* feat: populate draft ChannelVersion metadata during draft publishes

fill_published_fields now accepts an optional draft_channel_version parameter.
When provided, ChannelVersion-level fields are written to the draft object while
channel-level fields (total_resource_count, published_size, published_data,
version_info) are left untouched. mark_channel_version_as_distributable is also
skipped for draft publishes.

publish_channel now calls fill_published_fields in the draft branch, passing the
draft ChannelVersion returned by create_draft_channel_version.

Closes learningequality#5839

* refactor: simplify draft publish metadata implementation after review

* fix: use queryset update to bypass ChannelVersion full_clean validation

ChannelVersion.save() always calls full_clean(), which validates choices
on ArrayField(IntegerField(choices=...)) for included_licenses. Custom
license IDs used in existing tests (e.g. IDs 100, 101) are not in the
standard choices list from le_utils, so save() raised ValidationError.

Replace version_obj.save() with a queryset .update() to write metadata
fields directly without triggering model-level validation. The data
originates from the DB so validation is unnecessary, and M2M operations
(special_permissions_included) continue to use the model instance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: address review feedback on draft publish tests and publish_channel structure

- Merge redundant 'if not is_draft_version:' block into the 'else:' branch of
  the is_draft_version conditional in publish_channel, as requested by reviewer.

- Refactor FillPublishedFieldsDraftTestCase into DraftPublishChannelTestCase which
  tests the complete publish_channel flow (with save_export_database mocked) rather
  than calling fill_published_fields directly. Tests now use a Special Permissions
  license node with published=True to exercise the special_permissions_included logic.

- test_second_draft_publish_replaces_special_permissions_included now makes two real
  publish_channel calls with different license_description values and verifies the M2M
  is replaced (not accumulated) between calls.

- test_mark_channel_version_as_distributable_not_called replaced by
  test_special_permissions_distributable_false_for_draft_publish which asserts the
  distributable field stays False on the resulting AuditedSpecialPermissionsLicense
  objects after a draft publish of a public channel, rather than mocking the method.

* fix: replace channel.included_languages on publish instead of accumulating

Use .set() instead of .add() so languages removed from a channel are
cleared on subsequent publishes rather than accumulated indefinitely.

Flagged by AlexVelezLl, confirmed as a bug by rtibbles.

* fix: use get() instead of create() for Special Permissions license in tests

loadconstants inserts licenses with explicit PKs, leaving the PK sequence
at 1. Calling License.objects.create() in setUp() then collides with the
existing row. Use get() to fetch the pre-existing license — the same
pattern used in test_create_channel_versions.py and test_sync.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: move delete_public_channel_cache_keys into else branch of is_draft_version

The previous commit claimed to have merged all 'if not is_draft_version'
blocks into the else branch, but missed the delete_public_channel_cache_keys
call. Move it inside the else block and simplify the condition to 'if
channel.public'.

* fix: revert queryset update to version_obj.save() for ChannelVersion metadata

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Loosen the snapshot condition in ChannelVersion.save() to also fire when
version is None (draft rows), so every draft publish captures the current
channel name, description, thumbnail encoding, tagline, and language on
the draft ChannelVersion. Repeated draft publishes always reflect the
latest channel state.

Tests cover: first draft publish populates all four snapshot fields, and a
second draft publish after mutating channel metadata (including language
change from "en" to "fr") refreshes all four fields on the existing row.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Propagate is_draft_version from create_content_database into
map_channel_to_kolibri_channel so the exported content DB records
version=0 for draft publishes instead of channel.version + 1.

Kolibri's version-upgrade logic replaces any DB whose version is lower
than the incoming version, so version=0 ensures any real publish
(version >= 1) will always supersede a draft.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This includes:
- Updated translation files (.po and .json)
- Compiled Django messages (.mo files)
- Updated frontend i18n files
This includes:
- Updated translation files (.po and .json)
- Compiled Django messages (.mo files)
- Updated frontend i18n files
rtibbles and others added 16 commits May 7, 2026 22:59
…ri to avoid issues with autoformatting of downloaded i18n files.
…earningequality#5860)

* Remap Unit extra_fields.options node IDs when copying via copy_node

When a Unit topic (UNIT modality) is cloned, lesson node IDs and
pre/post test node IDs in extra_fields.options are rewritten to the
IDs of their copies. Entries whose source nodes were not part of the
copy operation (e.g. excluded via excluded_descendants) are dropped.

Adds _remap_unit_options() static helper on CustomContentNodeTreeManager,
calls it in _recurse_to_create_tree() for the deep-copy path and in
_copy() for the shallow-copy path. _deep_copy() and _copy() now return
(nodes, source_copy_id_map) so the map is available to callers;
copy_node() unwraps the tuple and returns only the node list.

* Test Unit extra_fields.options remapping across copy paths

Adds UnitCopyExtraFieldsTestCase covering:
- lesson_objectives keys remapped to cloned lesson PKs (deep and shallow)
- pre_test/post_test values remapped to cloned node PKs (deep and shallow)
- excluded lesson entry dropped from cloned Unit's lesson_objectives
- excluded Unit: copy succeeds with no remap error (deep and shallow)
- resource excluded inside lesson: lesson entry preserved (deep and shallow)
- assessment_objectives, learning_objectives, completion_criteria unchanged
- standalone Unit copy remaps its lesson children correctly
- extra_fields on Course and Lesson topics is copied verbatim

* Remove pre/post test remapping — Unit options contain no such node-ID keys

The pre_test/post_test entries in a Unit's options are assessment items
on the Unit node itself, not separate ContentNode IDs.  Remove the
dead code in _remap_unit_options() and the fantasy test coverage that
accompanied it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: drop test_standalone_unit_copy_remaps_lesson_children

Reviewer noted this test is redundant — the same behaviour is already
exercised by the deep/shallow copy integration tests for Unit nodes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Bumps [ajv](https://github.com/ajv-validator/ajv) from 8.17.1 to 8.18.0.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](ajv-validator/ajv@v8.17.1...v8.18.0)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 8.18.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 5 to 6.0.3.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](pnpm/action-setup@v5...v6.0.3)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.11.1 to 4.12.0.
- [Release notes](https://github.com/pytest-dev/pytest-django/releases)
- [Changelog](https://github.com/pytest-dev/pytest-django/blob/main/docs/changelog.rst)
- [Commits](pytest-dev/pytest-django@v4.11.1...v4.12.0)

---
updated-dependencies:
- dependency-name: pytest-django
  dependency-version: 4.12.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
VAutocomplete eagerly evaluates getText(internalValue) as the fallback
argument to getValue, even when the fallback is never used. In multiple
mode (used in SearchFilters), internalValue is an Array of selected ids.
Arrays are objects in JS, so getPropertyFromItem does not short-circuit
on the primitive guard and calls languageText with the array directly.
Since arrays have no native_name property, this throws a TypeError.

Guard languageText against item being null/undefined or lacking a
native_name (covers arrays, partial objects, and any future edge cases).

Fixes learningequality#5740.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

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

Prior findings

Unaddressed (re-raised below):

  • toBeDefined() vs toBeInTheDocument() in clickToolbarAction (nitpick) — no reply, code unchanged at HintsEditor.spec.js:57

0/1 prior findings resolved. 1 re-raised below.


CI has two failures — Python unit tests and Build all message files — both failing at "Install python dependencies with uv". The merge commit (90cf90d) left unresolved conflict markers in requirements.in and requirements.txt, which prevents uv from parsing those files. These files are also out of scope for this PR. Fix: rebase cleanly onto unstable, or resolve the conflicts by accepting the unstable side of both files.

No UI files changed — Phase 3 skipped.

Blocking: Unresolved merge conflicts in requirements.in and requirements.txt — see inline comments.
Nitpick (re-raised): toBeDefined() vs toBeInTheDocument() at HintsEditor.spec.js:57.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

Comment thread requirements.in
psycopg2-binary==2.9.11
django-js-reverse==0.10.2
django-registration==3.4
<<<<<<< HEAD
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.

blocking: This file has unresolved merge conflict markers committed by the merge commit (90cf90d). requirements.in is out of scope for this PR — it should not have been modified at all. Please rebase cleanly onto unstable (preferred), or resolve the conflict by keeping the unstable version of both le-utils and gunicorn and removing the conflict markers from this file and requirements.txt.

Comment thread requirements.txt
latex2mathml==3.78.1
# via -r requirements.in
<<<<<<< HEAD
le-utils==0.2.17
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.

blocking: Same unresolved merge conflict as requirements.in — conflict markers here prevent uv from parsing this file, causing the "Install python dependencies with uv" CI step to fail.

.trigger('click');
const clickToolbarAction = async ({ action, hintIdx, user }) => {
const buttons = screen.getAllByTestId(`toolbarIcon-${action}`);
expect(buttons[hintIdx]).toBeDefined();
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.

nitpick (re-raised from prior review): getAllByTestId already throws if no matching elements exist, so buttons[hintIdx] being present in the returned array is sufficient. Prefer expect(buttons[hintIdx]).toBeInTheDocument() — it's the idiomatic Testing Library assertion and produces a clearer failure message than toBeDefined().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Convert hints editor unit tests to Vue Testing Library

9 participants