Skip to content
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.
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 @@ -22,17 +22,43 @@
<mat-icon class="search-prefix-icon">search</mat-icon>
<div class="search-tokens-wrapper">
@for (chip of searchTerms(); track $index) {
<div class="search-chip">
<span class="chip-text">{{ chip }}</span>
<button
type="button"
class="remove-chip-btn"
(mousedown)="$event.preventDefault()"
(click)="onRemoveChip($index, $event)"
@if (editingIndex() === $index) {
<div class="search-chip editing" (click)="$event.stopPropagation()">
<input
#chipEditInput
type="text"
class="chip-edit-input"
[value]="editingText()"
(input)="onChipEditInput(chipEditInput.value)"
(blur)="onChipEditBlur()"
Comment thread
kyasbal marked this conversation as resolved.
Outdated
(keydown)="onChipEditKeyDown($event)"
/>
<button
type="button"
class="remove-chip-btn"
(mousedown)="$event.preventDefault()"
(click)="onRemoveChip($index, $event)"
>
<mat-icon>close</mat-icon>
</button>
</div>
} @else {
<div
class="search-chip"
matTooltip="Click to edit"
(click)="onChipClick($index, $event)"
>
Comment thread
kyasbal marked this conversation as resolved.
<mat-icon>close</mat-icon>
</button>
</div>
<span class="chip-text">{{ chip }}</span>
<button
type="button"
class="remove-chip-btn"
(mousedown)="$event.preventDefault()"
(click)="onRemoveChip($index, $event)"
>
<mat-icon>close</mat-icon>
</button>
</div>
}
<span class="or-separator">OR</span>
}
<input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ $input-placeholder-color: mat.m2-get-color-from-palette(

$chip-bg-color: mat.m2-get-color-from-palette(mat.$m2-indigo-palette, 50);
$chip-border-color: mat.m2-get-color-from-palette(mat.$m2-indigo-palette, 200);
$chip-hover-bg-color: mat.m2-get-color-from-palette(
mat.$m2-indigo-palette,
100
);
$chip-hover-border-color: mat.m2-get-color-from-palette(
mat.$m2-indigo-palette,
300
);
$chip-editing-bg-color: mat.m2-get-color-from-palette(mat.$m2-gray-palette, 50);
$chip-editing-border-color: mat.m2-get-color-from-palette(
mat.$m2-indigo-palette,
500
);
$chip-text-color: mat.m2-get-color-from-palette(mat.$m2-indigo-palette, 800);
$chip-remove-hover-color: mat.m2-get-color-from-palette(
mat.$m2-red-palette,
Expand Down Expand Up @@ -120,13 +133,40 @@ $clear-btn-hover-color: mat.m2-get-color-from-palette(
font-size: 12px;
color: $chip-text-color;
max-width: 200px;
cursor: pointer;
transition:
background-color 0.15s ease,
border-color 0.15s ease;

&:hover {
background-color: $chip-hover-bg-color;
border-color: $chip-hover-border-color;
}

&.editing {
background-color: $chip-editing-bg-color;
border-color: $chip-editing-border-color;
cursor: text;
}

.chip-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.chip-edit-input {
border: 0;
outline: none;
background: transparent;
font-size: 12px;
color: $chip-text-color;
padding: 0;
min-width: 24px;
max-width: 170px;
width: 100%;
}

.remove-chip-btn {
background: transparent;
border: 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,4 +244,245 @@ describe('ChipSearchBarComponent', () => {
expect(component.searchTerms()).toEqual([]);
expect(component.draft()).toBe('');
});

it('should switch chip to editing mode when clicked', () => {
fixture.componentRef.setInput('searchTerms', ['foo', 'bar']);
fixture.detectChanges();

const chipEls = fixture.debugElement.queryAll(By.css('.search-chip'));
chipEls[0].nativeElement.click();
fixture.detectChanges();

expect(component.editingIndex()).toBe(0);
expect(component.editingText()).toBe('foo');

const editInput = fixture.debugElement.query(By.css('.chip-edit-input'));
expect(editInput).toBeTruthy();
expect((editInput.nativeElement as HTMLInputElement).value).toBe('foo');
});

it('should commit edited chip on Enter key', () => {
fixture.componentRef.setInput('searchTerms', ['foo', 'bar']);
fixture.detectChanges();

const chipEls = fixture.debugElement.queryAll(By.css('.search-chip'));
chipEls[0].nativeElement.click();
fixture.detectChanges();

const editInput = fixture.debugElement.query(By.css('.chip-edit-input'))
.nativeElement as HTMLInputElement;
component.onChipEditInput('baz');
fixture.detectChanges();

editInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
fixture.detectChanges();

expect(component.searchTerms()).toEqual(['baz', 'bar']);
expect(component.editingIndex()).toBeNull();
expect(fixture.debugElement.query(By.css('.chip-edit-input'))).toBeNull();
});

it('should cancel editing and restore original text on Escape key', () => {
fixture.componentRef.setInput('searchTerms', ['foo']);
fixture.detectChanges();

const chipEl = fixture.debugElement.query(By.css('.search-chip'));
chipEl.nativeElement.click();
fixture.detectChanges();

const editInput = fixture.debugElement.query(By.css('.chip-edit-input'))
.nativeElement as HTMLInputElement;
component.onChipEditInput('edited');
fixture.detectChanges();

editInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
fixture.detectChanges();

expect(component.searchTerms()).toEqual(['foo']);
expect(component.editingIndex()).toBeNull();
});

it('should commit edited chip on blur', () => {
fixture.componentRef.setInput('searchTerms', ['foo']);
fixture.detectChanges();

const chipEl = fixture.debugElement.query(By.css('.search-chip'));
chipEl.nativeElement.click();
fixture.detectChanges();

const editInput = fixture.debugElement.query(By.css('.chip-edit-input'))
.nativeElement as HTMLInputElement;
component.onChipEditInput('blurred');
fixture.detectChanges();

editInput.dispatchEvent(new Event('blur'));
fixture.detectChanges();

expect(component.searchTerms()).toEqual(['blurred']);
expect(component.editingIndex()).toBeNull();
});

it('should remove chip if committed value is empty or whitespace only', () => {
fixture.componentRef.setInput('searchTerms', ['foo', 'bar']);
fixture.detectChanges();

const chipEls = fixture.debugElement.queryAll(By.css('.search-chip'));
chipEls[0].nativeElement.click();
fixture.detectChanges();

const editInput = fixture.debugElement.query(By.css('.chip-edit-input'))
.nativeElement as HTMLInputElement;
component.onChipEditInput(' ');
fixture.detectChanges();

editInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
fixture.detectChanges();

expect(component.searchTerms()).toEqual(['bar']);
expect(component.editingIndex()).toBeNull();
});

it('should split into multiple chips when delimiter is in edited chip text', () => {
fixture.componentRef.setInput('searchTerms', ['first', 'second']);
fixture.detectChanges();

const chipEls = fixture.debugElement.queryAll(By.css('.search-chip'));
chipEls[0].nativeElement.click();
fixture.detectChanges();

const editInput = fixture.debugElement.query(By.css('.chip-edit-input'))
.nativeElement as HTMLInputElement;
component.onChipEditInput('alpha | beta\ngamma');
fixture.detectChanges();

editInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
fixture.detectChanges();

expect(component.searchTerms()).toEqual([
'alpha',
'beta',
'gamma',
'second',
]);
expect(component.editingIndex()).toBeNull();
});

it('should remove chip and exit edit mode when remove button is clicked during edit', () => {
fixture.componentRef.setInput('searchTerms', ['chip1', 'chip2']);
fixture.detectChanges();

const chipEls = fixture.debugElement.queryAll(By.css('.search-chip'));
chipEls[0].nativeElement.click();
fixture.detectChanges();

expect(component.editingIndex()).toBe(0);

const removeBtn = fixture.debugElement.query(By.css('.remove-chip-btn'));
removeBtn.nativeElement.click();
fixture.detectChanges();

expect(component.searchTerms()).toEqual(['chip2']);
expect(component.editingIndex()).toBeNull();
});

it('should adjust editingIndex when a preceding chip is removed', () => {
fixture.componentRef.setInput('searchTerms', ['chip1', 'chip2', 'chip3']);
fixture.detectChanges();

const chipEls = fixture.debugElement.queryAll(By.css('.search-chip'));
chipEls[1].nativeElement.click();
fixture.detectChanges();

expect(component.editingIndex()).toBe(1);

const removeBtns = fixture.debugElement.queryAll(
By.css('.remove-chip-btn'),
);
removeBtns[0].nativeElement.click();
fixture.detectChanges();

expect(component.editingIndex()).toBe(0);
expect(component.searchTerms()).toEqual(['chip2', 'chip3']);
});
Comment thread
kyasbal marked this conversation as resolved.
Outdated

it('should clear editing state when clear button is clicked during edit', () => {
fixture.componentRef.setInput('searchTerms', ['foo']);
fixture.detectChanges();

const chipEl = fixture.debugElement.query(By.css('.search-chip'));
chipEl.nativeElement.click();
fixture.detectChanges();

expect(component.editingIndex()).toBe(0);

const clearBtn = fixture.debugElement.query(By.css('.clear-search-btn'));
clearBtn.nativeElement.click();
fixture.detectChanges();

expect(component.searchTerms()).toEqual([]);
expect(component.editingIndex()).toBeNull();
expect(component.editingText()).toBe('');
});

it('should commit main draft before starting chip edit', () => {
fixture.componentRef.setInput('searchTerms', ['foo']);
fixture.detectChanges();

component.onDraftInput('draft_term');
fixture.detectChanges();

const chipEl = fixture.debugElement.query(By.css('.search-chip'));
chipEl.nativeElement.click();
fixture.detectChanges();

expect(component.searchTerms()).toEqual(['foo', 'draft_term']);
expect(component.draft()).toBe('');
expect(component.editingIndex()).toBe(0);
});

it('should not close edit mode when clicking inside the active chip editor', () => {
fixture.componentRef.setInput('searchTerms', ['foo']);
fixture.detectChanges();

const chipEl = fixture.debugElement.query(By.css('.search-chip'));
chipEl.nativeElement.click();
fixture.detectChanges();

expect(component.editingIndex()).toBe(0);

const editingChipDiv = fixture.debugElement.query(
By.css('.search-chip.editing'),
);
const clickEvent = new MouseEvent('click', { bubbles: true });
spyOn(clickEvent, 'stopPropagation');
editingChipDiv.nativeElement.dispatchEvent(clickEvent);

expect(clickEvent.stopPropagation).toHaveBeenCalled();
expect(component.editingIndex()).toBe(0);
});

it('should adjust index and handle bounds check in startChipEdit', () => {
fixture.componentRef.setInput('searchTerms', ['first', 'second', 'third']);
fixture.detectChanges();

// Start editing 'first'
component.startChipEdit(0);
expect(component.editingIndex()).toBe(0);

// Empty 'first' so it gets removed, then start editing 'third' (originally index 2)
component.onChipEditInput(' ');
component.startChipEdit(2);

// Since 'first' was removed, previous index 2 is now index 1 ('third')
expect(component.searchTerms()).toEqual(['second', 'third']);
expect(component.editingIndex()).toBe(1);
expect(component.editingText()).toBe('third');

// Out of bounds startChipEdit should be ignored
component.startChipEdit(99);
expect(component.editingIndex()).toBe(1);

component.startChipEdit(-1);
expect(component.editingIndex()).toBe(1);
});
});
Loading
Loading