Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(attach-focus): restore behavior prior to #346 #349

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 3 additions & 9 deletions src/attach-focus.ts
@@ -1,7 +1,8 @@
import { bindingMode } from 'aurelia-binding';
import { customAttribute, ComponentAttached } from 'aurelia-templating';
import { DOM } from 'aurelia-pal';

@customAttribute('attach-focus')
@customAttribute('attach-focus', bindingMode.oneTime)
export class AttachFocus implements ComponentAttached {
public value: boolean | string;

Expand All @@ -15,14 +16,7 @@ export class AttachFocus implements ComponentAttached {
}

public attached(): void {
if (this.value && this.value !== 'false') {
this.element.focus();
}
}

public valueChanged(newValue: string) {
this.value = newValue;
if (this.value && this.value !== 'false') {
if (this.value === '' || (this.value && this.value !== 'false')) {
this.element.focus();
}
}
Expand Down
91 changes: 61 additions & 30 deletions test/unit/attach-focus.spec.ts
@@ -1,13 +1,25 @@
import { StageComponent } from 'aurelia-testing';
import { bootstrap } from 'aurelia-bootstrapper';
import { Container } from 'aurelia-dependency-injection';
import { Aurelia } from 'aurelia-framework';
import { TaskQueue } from 'aurelia-task-queue';
import { StageComponent, ComponentTester } from 'aurelia-testing';

describe('modal gets focused when attached', () => {
let component: any;
let viewModel: any;

describe('attach-focus', () => {
class ViewModel {
public first: any;
public focusTargetElement: Element;
public hasFocus: boolean;
}

let component: ComponentTester;
let viewModel: ViewModel;

function setupView(component: ComponentTester, attachFocusFragment: string): void {
component.inView(`
<div>
<input ${attachFocusFragment} ref="focusTargetElement" />
</div>
`);
}

beforeEach(() => {
Expand All @@ -18,50 +30,69 @@ describe('modal gets focused when attached', () => {
component.bootstrap((aurelia: Aurelia) => aurelia.use.basicConfiguration());
});

describe('when using attribute without .bind', () => {
beforeEach(() => {
component.inView(`
<div>
<input attach-focus="true" ref="noValueEl" />
</div>
`);
describe('without binding command', () => {
describe('sets focus', () => {
it('without value', done => {
setupView(component, 'attach-focus');
component.create(bootstrap).then(() => {
expect(document.activeElement).toBe(viewModel.focusTargetElement);
done();
}, done.fail);
});

it('when the value is "true"', done => {
setupView(component, 'attach-focus="true"');
component.create(bootstrap).then(() => {
expect(document.activeElement).toBe(viewModel.focusTargetElement);
done();
}, done.fail);
});
});

it('sets focus to no value element', done => {
it('does not set focus when the value is "false"', done => {
setupView(component, 'attach-focus="false"');
component.create(bootstrap).then(() => {
expect(document.activeElement).toBe(viewModel.noValueEl);
expect(document.activeElement).not.toBe(viewModel.focusTargetElement);
done();
});
}, done.fail);
});
});

describe('when binding to vm property', () => {
describe('with binding command', () => {
beforeEach(() => {
component.inView(`
<div>
<input attach-focus.bind="first" ref="firstEl" />
<input attach-focus.bind="!first" ref="secondEl" />
</div>
`);
setupView(component, 'attach-focus.bind="hasFocus"');
});

it('sets focus to first element when true', done => {
viewModel.first = true;
it('sets focus when the value is "true"', done => {
viewModel.hasFocus = true;
component.create(bootstrap).then(() => {
expect(document.activeElement).toBe(viewModel.firstEl);
expect(document.activeElement).toBe(viewModel.focusTargetElement);
done();
});
}, done.fail);
});

it('sets focus to second element when false', done => {
viewModel.first = false;
it('does not set focus when the value is "false"', done => {
viewModel.hasFocus = false;
component.create(bootstrap).then(() => {
expect(document.activeElement).toBe(viewModel.secondEl);
expect(document.activeElement).not.toBe(viewModel.focusTargetElement);
done();
});
}, done.fail);
});
});

it('sets focus only in ".attached"', done => {
setupView(component, 'attach-focus.to-view="hasFocus"');
viewModel.hasFocus = false;
component.create(bootstrap).then(() => {
expect(document.activeElement).not.toBe(viewModel.focusTargetElement);
viewModel.hasFocus = true;
(Container.instance.get(TaskQueue) as TaskQueue).queueMicroTask(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bigopon can you advice if there is a better way to await the change?

Copy link
Member

Choose a reason for hiding this comment

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

task queue has flushMicroTaskQueue() that can be used for this situation, to immediately apply the changes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bigopon I'll still need the TaskQueue. Anything in testing I can use?

expect(document.activeElement).not.toBe(viewModel.focusTargetElement);
done();
});
}, done.fail);
});

afterEach(() => {
component.dispose();
});
Expand Down