Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -1802,3 +1802,50 @@ if (DEBUG) {
}
);
}

// The bug this documents is the backtracking assertion, which only exists in
// debug builds; in production the listener succeeds and the todo would pass.
if (DEBUG) {
moduleFor(
'Dynamic content tests: disabling a focused element',
class extends RenderingTestCase {
async '@todo a {{on "blur"}} listener may update rendered state when the element becomes disabled (GH#19287)'(
assert
) {
let error;

this.render(
'<span>{{this.isFocused}}</span><textarea disabled={{this.isDisabled}} {{on "focus" this.onFocus}} {{on "blur" this.onBlur}}></textarea>',
{
isDisabled: false,
isFocused: false,
onFocus: () => set(this.context, 'isFocused', true),
onBlur: () => {
try {
set(this.context, 'isFocused', false);
} catch (e) {
error = e;
}
},
}
);

let textarea = this.element.querySelector('textarea');

runTask(() => textarea.focus());
assert.strictEqual(this.context.isFocused, true, 'precond - focus listener ran');

runTask(() => set(this.context, 'isDisabled', true));

await new Promise((resolve) => setTimeout(resolve, 50));

assert.strictEqual(
error,
undefined,
'no backtracking assertion was thrown from the blur listener'
);
assert.strictEqual(this.context.isFocused, false, 'blur listener updated the state');
}
}
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { moduleFor, RenderingTestCase, runTask } from 'internal-test-helpers';
import { setTesting } from '@ember/debug';
import { getInternalModifierManager, setComponentTemplate } from '@glimmer/manager';
import { on } from '@glimmer/runtime';
import { precompileTemplate } from '@ember/template-compilation';
Expand Down Expand Up @@ -296,6 +297,53 @@ moduleFor(
this.assertStableRerender();
this.assertCounts({ adds: 2, removes: 0 });
}

async '@todo all listeners for an event run even when an earlier one removes the element (GH#19344)'(
assert
) {
let sequence = [];

this.render(
'{{#if this.showBox}}<div id="scrollbox" style="overflow: scroll; height: 50px; width: 50px;" {{on "scroll" this.first}} {{on "scroll" this.second}}><div style="height: 500px;"></div></div>{{/if}}',
{
showBox: true,
first: () => {
sequence.push('first');
this.context.set('showBox', false);
},
second: () => sequence.push('second'),
}
);

let box = this.element.querySelector('#scrollbox');

// Testing mode disables the autorun that makes this reproducible in
// apps: with autorun enabled, the flush is scheduled as a microtask,
// and browser-dispatched events (unlike synthetic el.click()) run a
// microtask checkpoint between listener callbacks. The flush tears
// down the element's modifiers mid-dispatch, and the removed second
// listener is never invoked.
setTesting(false);
try {
box.scrollTop = 30;

await new Promise((resolve) => {
let attempts = 0;
(function check() {
if (sequence.length >= 2 || ++attempts > 50) {
resolve();
} else {
setTimeout(check, 10);
}
})();
});
} finally {
setTesting(true);
}

assert.strictEqual(this.element.querySelector('#scrollbox'), null, 'the element was removed');
assert.deepEqual(sequence, ['first', 'second'], 'both listeners ran, in order');
}
}
);

Expand Down
4 changes: 4 additions & 0 deletions packages/internal-test-helpers/lib/module-for.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ export function setupTestClass<T extends TestCase, G extends Generator>(
QUnit.skip(name.slice(5), function (this: TestContext<T>, assert) {
return (this.instance![name] as any)(assert);
});
} else if (name.indexOf('@todo ') === 0) {
QUnit.todo(name.slice(5), function (this: TestContext<T>, assert) {
return (this.instance![name] as any)(assert);
});
} else {
let match = /^@feature\(([A-Z_a-z-! ,]+)\) /.exec(name);

Expand Down
Loading