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
1 change: 1 addition & 0 deletions src/tedi/components/content/table/table-demo.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ function editableLocationCellTemplate(tplName = "locationCell"): string {
<tedi-select
[inputId]="'location-' + ctx.row.original.id"
size="small"
[clearable]="false"
[options]="counties"
bindLabel="label"
bindValue="value"
Expand Down
277 changes: 277 additions & 0 deletions src/tedi/components/form/select/select.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3794,3 +3794,280 @@ describe("SelectComponent existing-behaviour guards", () => {
expect(labelText(fixture)).toBe("10");
});
});

@Component({
standalone: true,
imports: [SelectComponent, ReactiveFormsModule],
template: `
<tedi-select
id="default"
inputId="default"
[options]="options"
[formControl]="defaultControl"
/>
<tedi-select
id="bare"
inputId="bare"
[options]="options"
[formControl]="bareControl"
clearable
/>
<tedi-select
id="off"
inputId="off"
[options]="options"
[formControl]="offControl"
clearable="false"
/>
`,
})
class ClearableDefaultsHostComponent {
options = [{ label: "Option 1", value: "Option 1" }];
defaultControl = new FormControl<unknown>("Option 1");
bareControl = new FormControl<unknown>("Option 1");
offControl = new FormControl<unknown>("Option 1");
}

describe("SelectComponent clearable", () => {
let hostEl: HTMLElement;

beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [ClearableDefaultsHostComponent],
providers: [{ provide: TEDI_TRANSLATION_DEFAULT_TOKEN, useValue: "et" }],
});
const fixture = TestBed.createComponent(ClearableDefaultsHostComponent);
hostEl = fixture.nativeElement;
fixture.detectChanges();
tick();
fixture.detectChanges();
}));

const clearButton = (id: string) =>
hostEl.querySelector(`#${id} .tedi-select__clear`);

it("shows a clear button by default once a value is selected", () => {
expect(clearButton("default")).toBeTruthy();
});

it("treats a bare clearable attribute as true", () => {
expect(clearButton("bare")).toBeTruthy();
});

it('treats clearable="false" as false', () => {
expect(clearButton("off")).toBeNull();
});
});

@Component({
standalone: true,
imports: [SelectComponent, ReactiveFormsModule],
template: `
<tedi-select
id="no-empty-option"
inputId="no-empty-option"
placeholder="Vali..."
[options]="cities"
bindLabel="label"
bindValue="value"
[formControl]="emptyString"
/>
<tedi-select
id="empty-option"
inputId="empty-option"
placeholder="Vali..."
[options]="statuses"
bindLabel="label"
bindValue="value"
[formControl]="allStatuses"
/>
<tedi-select
id="multiple"
inputId="multiple"
placeholder="Vali..."
[allowMultiple]="true"
[options]="cities"
bindLabel="label"
bindValue="value"
[formControl]="multipleEmpty"
/>
`,
})
class EmptyStringHostComponent {
cities = [
{ label: "Tallinn", value: "tallinn" },
{ label: "Tartu", value: "tartu" },
];
statuses = [
{ label: "Kõik", value: "" },
{ label: "Aktiivne", value: "active" },
];
emptyString = new FormControl<string>("");
allStatuses = new FormControl<string>("");
multipleEmpty = new FormControl<string[]>([""]);
}

describe("SelectComponent with an empty-string value", () => {
let fixture: ComponentFixture<EmptyStringHostComponent>;
let host: EmptyStringHostComponent;

const render = () => {
fixture.detectChanges();
tick();
fixture.detectChanges();
};

beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [EmptyStringHostComponent],
providers: [{ provide: TEDI_TRANSLATION_DEFAULT_TOKEN, useValue: "et" }],
});
fixture = TestBed.createComponent(EmptyStringHostComponent);
host = fixture.componentInstance;
render();
}));

const el = (id: string) =>
fixture.nativeElement.querySelector(`#${id}`) as HTMLElement;
const clearButton = (id: string) =>
el(id).querySelector(".tedi-select__clear");
const shownText = (id: string) =>
el(id).querySelector(".tedi-select__trigger")?.textContent?.trim();

it("shows a '' form value as empty, with no clear button", () => {
expect(clearButton("no-empty-option")).toBeNull();
expect(shownText("no-empty-option")).toContain("Vali...");
});

it("leaves the stored value as ''", () => {
expect(host.emptyString.value).toBe("");
});

it("shows '' as empty even when an option has that value", () => {
expect(clearButton("empty-option")).toBeNull();
expect(shownText("empty-option")).toContain("Vali...");
});

it("resets when the option with value '' is picked", fakeAsync(() => {
host.allStatuses.setValue("active");
render();
expect(clearButton("empty-option")).toBeTruthy();

(
el("empty-option").querySelector(".tedi-select__trigger") as HTMLElement
).click();
render();
const all = Array.from(
document.querySelectorAll(".tedi-dropdown-item"),
).find((item) => item.textContent?.trim() === "Kõik") as HTMLElement;
all.click();
render();

expect(host.allStatuses.value).toBe("");
expect(clearButton("empty-option")).toBeNull();
expect(shownText("empty-option")).toContain("Vali...");
}));

it("resets through the virtual-scroll path too", fakeAsync(() => {
host.allStatuses.setValue("active");
render();
const select = fixture.debugElement.query(By.css("#empty-option"))
.componentInstance as SelectComponent;

select.onVirtualOptionClick(select.normalizedOptions()[0]);
render();

expect(select.selectedValues()).toEqual([]);
expect(host.allStatuses.value).toBe("");
}));

it("shows [''] as empty in a multiple select", () => {
expect(clearButton("multiple")).toBeNull();
expect(shownText("multiple")).toContain("Vali...");
});
});

@Component({
standalone: true,
imports: [SelectComponent, ReactiveFormsModule],
template: `
<tedi-select
inputId="multi-empty-option"
[allowMultiple]="true"
[showSelectAll]="true"
[selectableGroups]="true"
groupBy="group"
[options]="options"
bindLabel="label"
bindValue="value"
[formControl]="control"
/>
`,
})
class MultiEmptyOptionHostComponent {
options = [
{ label: "None", value: "", group: "A" },
{ label: "One", value: "one", group: "A" },
{ label: "Two", value: "two", group: "A" },
];
control = new FormControl<string[]>([]);
}

describe("SelectComponent multiselect with an option whose value is ''", () => {
let fixture: ComponentFixture<MultiEmptyOptionHostComponent>;
let host: MultiEmptyOptionHostComponent;
let select: SelectComponent;

const render = () => {
fixture.detectChanges();
tick();
fixture.detectChanges();
};

beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [MultiEmptyOptionHostComponent],
providers: [{ provide: TEDI_TRANSLATION_DEFAULT_TOKEN, useValue: "et" }],
});
fixture = TestBed.createComponent(MultiEmptyOptionHostComponent);
host = fixture.componentInstance;
render();
select = fixture.debugElement.query(By.directive(SelectComponent))
.componentInstance as SelectComponent;
}));

it("never selects the '' option on click", fakeAsync(() => {
select.onVirtualOptionClick(select.normalizedOptions()[0]);
render();
expect(select.selectedValues()).toEqual([]);
expect(host.control.value).toEqual([]);
}));

it("never selects it through the listbox either", fakeAsync(() => {
select.handleValueChange({ value: ["", "one"] });
render();
expect(select.selectedValues()).toEqual(["one"]);
expect(host.control.value).toEqual(["one"]);
}));

it("leaves it out of select all, so select all can be toggled off again", fakeAsync(() => {
select.onVirtualSelectAllClick();
render();
expect(select.selectedValues()).toEqual(["one", "two"]);
expect(select.allOptionsSelected()).toBe(true);

select.onVirtualSelectAllClick();
render();
expect(select.selectedValues()).toEqual([]);
}));

it("leaves it out of group selection", fakeAsync(() => {
select.handleValueChange({
value: [SpecialOptionControls.SELECT_GROUP + "A"],
});
render();
expect(select.selectedValues()).toEqual(["one", "two"]);
expect(select.isGroupSelected("A")).toBe(true);
expect(select.isGroupIndeterminate("A")).toBe(false);
}));
});
Loading
Loading