Skip to content

Commit 113ae56

Browse files
rtibblesbotclaude
andcommitted
fix: emit update:interaction only when the built content changes
Entering edit mode no longer emits. The workingInteraction watcher compares by value, so reopening an editor or retyping identical text is not an update. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 7ce8f7c commit 113ae56

2 files changed

Lines changed: 25 additions & 13 deletions

File tree

‎contentcuration/contentcuration/frontend/shared/views/QTIEditor/interactions/associate/AssociateInteractionEditor.vue‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@
337337
338338
import { computed, ref, watch } from 'vue';
339339
import flatten from 'lodash/flatten';
340+
import isEqual from 'lodash/isEqual';
340341
import shuffle from 'lodash/shuffle';
341342
import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow';
342343
import { themeTokens, themePalette } from 'kolibri-design-system/lib/styles/theme';
@@ -539,13 +540,15 @@
539540
} else if (state.value.pairs.length > 0) {
540541
openPairItem(0, 0);
541542
}
542-
emit('update:interaction', workingInteraction.value);
543543
},
544544
{ immediate: true },
545545
);
546546
547-
watch(workingInteraction, newVal => {
548-
if (props.mode === 'edit') emit('update:interaction', newVal);
547+
// Rebuilding yields a fresh object on every state change, so compare by
548+
// value — reopening an editor or retyping the same text is not a change.
549+
watch(workingInteraction, (newVal, oldVal) => {
550+
if (props.mode !== 'edit' || isEqual(newVal, oldVal)) return;
551+
emit('update:interaction', newVal);
549552
});
550553
551554
const errorCodes = computed(() => errors.value.map(e => e.code));

‎contentcuration/contentcuration/frontend/shared/views/QTIEditor/interactions/associate/__tests__/AssociateInteractionEditor.spec.js‎

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ const renderEditor = (props = {}) =>
5858
routes: new VueRouter(),
5959
});
6060

61-
const latestBodyXml = emitted => emitted()['update:interaction'].at(-1)[0].bodyXml;
61+
const updates = emitted => emitted()['update:interaction'] || [];
62+
const latestBodyXml = emitted => updates(emitted).at(-1)[0].bodyXml;
6263

6364
describe('AssociateInteractionEditor', () => {
6465
describe('edit mode rendering', () => {
@@ -191,14 +192,14 @@ describe('AssociateInteractionEditor', () => {
191192
const { emitted } = renderEditor();
192193
await user.click(button(tr.$tr('addDistractorBtn')));
193194
await user.type(openTextarea(), 'Demetrius');
194-
const before = emitted()['update:interaction'].length;
195+
const before = updates(emitted).length;
195196
// Editing something else closes the editor, which is what commits it.
196197
await user.click(button(editPairItem(1, 1)));
197198

198199
const pool = screen.getByRole('list', { name: tr.$tr('distractorsLabel') });
199200
expect(within(pool).getByText('Demetrius')).toBeInTheDocument();
200201
expect(latestBodyXml(emitted)).toContain('Demetrius');
201-
expect(emitted()['update:interaction'].length).toBeGreaterThan(before);
202+
expect(updates(emitted).length).toBeGreaterThan(before);
202203
});
203204

204205
it('commits the written distractor when the add button is clicked again', async () => {
@@ -333,24 +334,32 @@ describe('AssociateInteractionEditor', () => {
333334
});
334335

335336
describe('emits', () => {
336-
it('emits update:interaction on mount in edit mode', () => {
337+
it('does not emit update:interaction on mount in edit mode', () => {
337338
const { emitted } = renderEditor();
338-
const payload = emitted()['update:interaction'][0][0];
339+
expect(updates(emitted)).toHaveLength(0);
340+
});
341+
342+
it('emits update:interaction after adding a pair', async () => {
343+
const user = userEvent.setup();
344+
const { emitted } = renderEditor();
345+
await user.click(button(tr.$tr('addPairBtn')));
346+
const payload = updates(emitted).at(-1)[0];
339347
expect(typeof payload.bodyXml).toBe('string');
340348
expect(Array.isArray(payload.responseDeclarations)).toBe(true);
341349
});
342350

343-
it('emits update:interaction again after adding a pair', async () => {
351+
it('does not emit update:interaction when moving between editors changes nothing', async () => {
344352
const user = userEvent.setup();
345353
const { emitted } = renderEditor();
346-
const before = emitted()['update:interaction'].length;
347-
await user.click(button(tr.$tr('addPairBtn')));
348-
expect(emitted()['update:interaction'].length).toBeGreaterThan(before);
354+
// Pair 1 item 1 opens on mount; moving the open editor is not a content change.
355+
await user.click(button(editPairItem(1, 2)));
356+
await user.click(button(editPairItem(2, 1)));
357+
expect(updates(emitted)).toHaveLength(0);
349358
});
350359

351360
it('does not emit update:interaction in view mode', () => {
352361
const { emitted } = renderEditor({ mode: 'view', showAnswers: true });
353-
expect(emitted()['update:interaction']).toBeFalsy();
362+
expect(updates(emitted)).toHaveLength(0);
354363
});
355364
});
356365

0 commit comments

Comments
 (0)