Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
liamwhite committed Nov 15, 2024
2 parents 1e96a97 + 282b8b7 commit 77cc099
Show file tree
Hide file tree
Showing 15 changed files with 500 additions and 92 deletions.
99 changes: 99 additions & 0 deletions assets/js/__tests__/search.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { $ } from '../utils/dom';
import { assertNotNull } from '../utils/assert';
import { setupSearch } from '../search';
import { setupTagListener } from '../tagsinput';

const formData = `<form class="js-search-form">
<input type="text" class="js-search-field">
<a data-search-prepend="-">NOT</a>
<a data-search-add="id.lte:10" data-search-select-last="2" data-search-show-help="numeric">Numeric ID</a>
<a data-search-add="my:faves" data-search-show-help=" ">My favorites</a>
<div class="hidden" data-search-help="boolean">
<span class="js-search-help-subject"></span> is a Boolean value field
</div>
<div class="hidden" data-search-help="numeric">
<span class="js-search-help-subject"></span> is a numerical range field
</div>
</form>`;

describe('Search form help', () => {
beforeAll(() => {
setupSearch();
setupTagListener();
});

let input: HTMLInputElement;
let prependAnchor: HTMLAnchorElement;
let idAnchor: HTMLAnchorElement;
let favesAnchor: HTMLAnchorElement;
let helpNumeric: HTMLDivElement;
let subjectSpan: HTMLElement;

beforeEach(() => {
document.body.innerHTML = formData;

input = assertNotNull($<HTMLInputElement>('input'));
prependAnchor = assertNotNull($<HTMLAnchorElement>('a[data-search-prepend]'));
idAnchor = assertNotNull($<HTMLAnchorElement>('a[data-search-add="id.lte:10"]'));
favesAnchor = assertNotNull($<HTMLAnchorElement>('a[data-search-add="my:faves"]'));
helpNumeric = assertNotNull($<HTMLDivElement>('[data-search-help="numeric"]'));
subjectSpan = assertNotNull($<HTMLSpanElement>('span', helpNumeric));
});

it('should add text to input field', () => {
idAnchor.click();
expect(input.value).toBe('id.lte:10');

favesAnchor.click();
expect(input.value).toBe('id.lte:10, my:faves');
});

it('should focus and select text in input field when requested', () => {
idAnchor.click();
expect(input).toHaveFocus();
expect(input.selectionStart).toBe(7);
expect(input.selectionEnd).toBe(9);
});

it('should highlight subject name when requested', () => {
expect(helpNumeric).toHaveClass('hidden');
idAnchor.click();
expect(helpNumeric).not.toHaveClass('hidden');
expect(subjectSpan).toHaveTextContent('Numeric ID');
});

it('should not focus and select text in input field when unavailable', () => {
favesAnchor.click();
expect(input).not.toHaveFocus();
expect(input.selectionStart).toBe(8);
expect(input.selectionEnd).toBe(8);
});

it('should not highlight subject name when unavailable', () => {
favesAnchor.click();
expect(helpNumeric).toHaveClass('hidden');
});

it('should prepend to empty input', () => {
prependAnchor.click();
expect(input.value).toBe('-');
});

it('should prepend to single input', () => {
input.value = 'a';
prependAnchor.click();
expect(input.value).toBe('-a');
});

it('should prepend to comma-separated input', () => {
input.value = 'a,b';
prependAnchor.click();
expect(input.value).toBe('a,-b');
});

it('should prepend to comma and space-separated input', () => {
input.value = 'a, b';
prependAnchor.click();
expect(input.value).toBe('a, -b');
});
});
188 changes: 188 additions & 0 deletions assets/js/__tests__/tagsinput.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { $, $$, hideEl } from '../utils/dom';
import { assertNotNull } from '../utils/assert';
import { TermSuggestion } from '../utils/suggestions';
import { setupTagsInput, addTag, reloadTagsInput } from '../tagsinput';

const formData = `<form class="tags-form">
<div class="js-tag-block fancy-tag-upload">
<textarea class="js-taginput js-taginput-plain"></textarea>
<div class="js-taginput js-taginput-fancy">
<input type="text" class="js-taginput-input" placeholder="add a tag">
</div>
</div>
<button class="js-taginput-show">Fancy Editor</button>
<button class="js-taginput-hide hidden">Plain Editor</button>
<input type="submit" value="Save Tags">
</form>`;

describe('Fancy tags input', () => {
let form: HTMLFormElement;
let tagBlock: HTMLDivElement;
let plainInput: HTMLTextAreaElement;
let fancyInput: HTMLDivElement;
let fancyText: HTMLInputElement;
let fancyShowButton: HTMLButtonElement;
let plainShowButton: HTMLButtonElement;

beforeEach(() => {
window.booru.fancyTagUpload = true;
window.booru.fancyTagEdit = true;
document.body.innerHTML = formData;

form = assertNotNull($<HTMLFormElement>('.tags-form'));
tagBlock = assertNotNull($<HTMLDivElement>('.js-tag-block'));
plainInput = assertNotNull($<HTMLTextAreaElement>('.js-taginput-plain'));
fancyInput = assertNotNull($<HTMLDivElement>('.js-taginput-fancy'));
fancyText = assertNotNull($<HTMLInputElement>('.js-taginput-input'));
fancyShowButton = assertNotNull($<HTMLButtonElement>('.js-taginput-show'));
plainShowButton = assertNotNull($<HTMLButtonElement>('.js-taginput-hide'));

// prevent these from submitting the form
fancyShowButton.addEventListener('click', e => e.preventDefault());
plainShowButton.addEventListener('click', e => e.preventDefault());
});

for (let i = 0; i < 4; i++) {
const type = (i & 2) === 0 ? 'upload' : 'edit';
const name = (i & 2) === 0 ? 'fancyTagUpload' : 'fancyTagEdit';
const value = (i & 1) === 0;

// eslint-disable-next-line no-loop-func
it(`should imply ${name}:${value} <-> ${type}:${value} on setup`, () => {
window.booru.fancyTagEdit = false;
window.booru.fancyTagUpload = false;
window.booru[name] = value;

plainInput.value = 'a, b';
tagBlock.classList.remove('fancy-tag-edit', 'fancy-tag-upload');
tagBlock.classList.add(`fancy-tag-${type}`);
expect($$('span.tag', fancyInput)).toHaveLength(0);

setupTagsInput(tagBlock);
expect($$('span.tag', fancyInput)).toHaveLength(value ? 2 : 0);
});
}

it('should move tags from the plain to the fancy editor when the fancy editor is shown', () => {
expect($$('span.tag', fancyInput)).toHaveLength(0);

setupTagsInput(tagBlock);
plainInput.value = 'a, b';
fancyShowButton.click();
expect($$('span.tag', fancyInput)).toHaveLength(2);
});

it('should move tags from the plain to the fancy editor on reload event', () => {
expect($$('span.tag', fancyInput)).toHaveLength(0);

setupTagsInput(tagBlock);
plainInput.value = 'a, b';
reloadTagsInput(plainInput);
expect($$('span.tag', fancyInput)).toHaveLength(2);
});

it('should respond to addtag events', () => {
setupTagsInput(tagBlock);
addTag(plainInput, 'a');
expect($$('span.tag', fancyInput)).toHaveLength(1);
});

it('should not respond to addtag events if the container is hidden', () => {
setupTagsInput(tagBlock);
hideEl(fancyInput);
addTag(plainInput, 'a');
expect($$('span.tag', fancyInput)).toHaveLength(0);
});

it('should respond to autocomplete events', () => {
setupTagsInput(tagBlock);
fancyText.dispatchEvent(new CustomEvent<TermSuggestion>('autocomplete', { detail: { value: 'a', label: 'a' } }));
expect($$('span.tag', fancyInput)).toHaveLength(1);
});

it('should allow removing previously added tags by clicking them', () => {
setupTagsInput(tagBlock);
addTag(plainInput, 'a');
assertNotNull($<HTMLAnchorElement>('span.tag a', fancyInput)).click();
expect($$('span.tag', fancyInput)).toHaveLength(0);
});

it('should allow removing previously added tags by adding one with a minus sign prepended', () => {
setupTagsInput(tagBlock);
addTag(plainInput, 'a');
expect($$('span.tag', fancyInput)).toHaveLength(1);
addTag(plainInput, '-a');
expect($$('span.tag', fancyInput)).toHaveLength(0);
});

it('should disallow adding empty tags', () => {
setupTagsInput(tagBlock);
addTag(plainInput, '');
expect($$('span.tag', fancyInput)).toHaveLength(0);
});

it('should disallow adding existing tags', () => {
setupTagsInput(tagBlock);
addTag(plainInput, 'a');
addTag(plainInput, 'a');
expect($$('span.tag', fancyInput)).toHaveLength(1);
});

it('should submit the form on ctrl+enter', () => {
setupTagsInput(tagBlock);

const ev = new KeyboardEvent('keydown', { keyCode: 13, ctrlKey: true, bubbles: true });

return new Promise<void>(resolve => {
form.addEventListener('submit', e => {
e.preventDefault();
resolve();
});

fancyText.dispatchEvent(ev);
expect(ev.defaultPrevented).toBe(true);
});
});

it('does nothing when backspacing on empty input and there are no tags', () => {
setupTagsInput(tagBlock);

const ev = new KeyboardEvent('keydown', { keyCode: 8, bubbles: true });
fancyText.dispatchEvent(ev);

expect($$('span.tag', fancyInput)).toHaveLength(0);
});

it('erases the last added tag when backspacing on empty input', () => {
setupTagsInput(tagBlock);
addTag(plainInput, 'a');
addTag(plainInput, 'b');

const ev = new KeyboardEvent('keydown', { keyCode: 8, bubbles: true });
fancyText.dispatchEvent(ev);

expect($$('span.tag', fancyInput)).toHaveLength(1);
});

it('adds new tag when comma is pressed', () => {
setupTagsInput(tagBlock);

const ev = new KeyboardEvent('keydown', { keyCode: 188, bubbles: true });
fancyText.value = 'a';
fancyText.dispatchEvent(ev);

expect($$('span.tag', fancyInput)).toHaveLength(1);
expect(fancyText.value).toBe('');
});

it('adds new tag when enter is pressed', () => {
setupTagsInput(tagBlock);

const ev = new KeyboardEvent('keydown', { keyCode: 13, bubbles: true });
fancyText.value = 'a';
fancyText.dispatchEvent(ev);

expect($$('span.tag', fancyInput)).toHaveLength(1);
expect(fancyText.value).toBe('');
});
});
8 changes: 8 additions & 0 deletions assets/js/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ function restoreOriginalValue() {

if (isSearchField(inputField) && originalQuery) {
inputField.value = originalQuery;

if (selectedTerm) {
const [, selectedTermEnd] = selectedTerm[0];

inputField.setSelectionRange(selectedTermEnd, selectedTermEnd);
}

return;
}

if (originalTerm) {
Expand Down
45 changes: 0 additions & 45 deletions assets/js/search.js

This file was deleted.

Loading

0 comments on commit 77cc099

Please sign in to comment.