mirror of
https://github.com/simonw/datasette.git
synced 2026-05-27 12:34:37 +02:00
199 lines
6.3 KiB
Python
199 lines
6.3 KiB
Python
import json
|
|
import subprocess
|
|
import textwrap
|
|
|
|
|
|
def test_navigation_search_tracks_and_renders_recent_items():
|
|
script = textwrap.dedent("""
|
|
const fs = require("fs");
|
|
const vm = require("vm");
|
|
|
|
class FakeElement {
|
|
constructor() {
|
|
this.innerHTML = "";
|
|
this.value = "";
|
|
this.dataset = {};
|
|
this.open = false;
|
|
}
|
|
addEventListener() {}
|
|
close() { this.open = false; }
|
|
focus() {}
|
|
querySelector() {
|
|
return { scrollIntoView() {} };
|
|
}
|
|
showModal() { this.open = true; }
|
|
}
|
|
|
|
class FakeShadowRoot {
|
|
constructor() {
|
|
this.innerHTML = "";
|
|
this.dialog = new FakeElement();
|
|
this.input = new FakeElement();
|
|
this.results = new FakeElement();
|
|
}
|
|
querySelector(selector) {
|
|
if (selector == "dialog") return this.dialog;
|
|
if (selector == ".search-input") return this.input;
|
|
if (selector == ".results-container") return this.results;
|
|
return new FakeElement();
|
|
}
|
|
}
|
|
|
|
global.HTMLElement = class {
|
|
constructor() {
|
|
this.attributes = {};
|
|
}
|
|
attachShadow() {
|
|
this.shadowRoot = new FakeShadowRoot();
|
|
return this.shadowRoot;
|
|
}
|
|
dispatchEvent() {}
|
|
getAttribute(name) {
|
|
return this.attributes[name] || null;
|
|
}
|
|
querySelector() {
|
|
return null;
|
|
}
|
|
setAttribute(name, value) {
|
|
this.attributes[name] = value;
|
|
}
|
|
};
|
|
global.CustomEvent = class {
|
|
constructor(name, options) {
|
|
this.name = name;
|
|
this.options = options;
|
|
}
|
|
};
|
|
global.customElements = {
|
|
registry: new Map(),
|
|
define(name, cls) {
|
|
this.registry.set(name, cls);
|
|
},
|
|
};
|
|
global.document = {
|
|
addEventListener() {},
|
|
activeElement: null,
|
|
createElement() {
|
|
return {
|
|
set textContent(value) {
|
|
this.innerHTML = String(value)
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """);
|
|
},
|
|
};
|
|
},
|
|
};
|
|
global.localStorage = {
|
|
store: {},
|
|
getItem(key) {
|
|
return Object.prototype.hasOwnProperty.call(this.store, key)
|
|
? this.store[key]
|
|
: null;
|
|
},
|
|
setItem(key, value) {
|
|
this.store[key] = String(value);
|
|
},
|
|
removeItem(key) {
|
|
delete this.store[key];
|
|
},
|
|
};
|
|
global.window = { location: { href: "" } };
|
|
|
|
vm.runInThisContext(
|
|
fs.readFileSync("datasette/static/navigation-search.js", "utf8"),
|
|
{ filename: "navigation-search.js" }
|
|
);
|
|
|
|
const Component = customElements.registry.get("navigation-search");
|
|
const element = new Component();
|
|
const items = Array.from({ length: 6 }, (_, index) => ({
|
|
name: `Item ${index + 1}`,
|
|
url: `/item-${index + 1}`,
|
|
type: "table",
|
|
description: "Table",
|
|
}));
|
|
items[5].name = "content: recent_datasette_releases";
|
|
items[5].display_name = "Recent Datasette releases";
|
|
|
|
for (const item of items) {
|
|
element.matches = [item];
|
|
element.renderedMatches = [item];
|
|
element.selectedIndex = 0;
|
|
element.selectCurrentItem();
|
|
}
|
|
|
|
const stored = JSON.parse(
|
|
Object.values(localStorage.store).find((value) => value.includes("/item-6"))
|
|
);
|
|
if (stored.length !== 5) {
|
|
throw new Error(`Expected 5 recent items, got ${stored.length}`);
|
|
}
|
|
if (stored[0].url !== "/item-6" || stored[4].url !== "/item-2") {
|
|
throw new Error(`Unexpected recent order: ${JSON.stringify(stored)}`);
|
|
}
|
|
if (stored[0].display_name !== "Recent Datasette releases") {
|
|
throw new Error(`Missing display_name in recent item: ${JSON.stringify(stored[0])}`);
|
|
}
|
|
|
|
element.matches = [
|
|
items[5],
|
|
items[4],
|
|
{
|
|
name: "Other",
|
|
url: "/other",
|
|
type: "database",
|
|
description: "Database",
|
|
},
|
|
];
|
|
element.shadowRoot.input.value = "";
|
|
element.renderResults();
|
|
|
|
const html = element.shadowRoot.results.innerHTML;
|
|
if (!html.includes("Recent")) {
|
|
throw new Error(`Missing Recent heading: ${html}`);
|
|
}
|
|
if (!html.includes("Recent Datasette releases") || !html.includes("Item 5")) {
|
|
throw new Error(`Missing recent items: ${html}`);
|
|
}
|
|
if (!html.includes("content: recent_datasette_releases")) {
|
|
throw new Error(`Missing canonical item name for display_name item: ${html}`);
|
|
}
|
|
if (!html.includes("Item 4") || !html.includes("Item 2")) {
|
|
throw new Error(`Expected all stored recent items in empty state: ${html}`);
|
|
}
|
|
if (html.includes("Other")) {
|
|
throw new Error(`Rendered non-recent item in empty state: ${html}`);
|
|
}
|
|
if (!html.includes("Clear recent")) {
|
|
throw new Error(`Missing Clear recent control: ${html}`);
|
|
}
|
|
|
|
element.clearRecentItems();
|
|
if (localStorage.getItem(element.recentItemsStorageKey()) !== null) {
|
|
throw new Error("Expected recent items to be cleared");
|
|
}
|
|
element.renderResults();
|
|
if (element.shadowRoot.results.innerHTML.includes("Clear recent")) {
|
|
throw new Error("Clear recent should disappear after clearing");
|
|
}
|
|
|
|
process.stdout.write(JSON.stringify(stored));
|
|
""")
|
|
result = subprocess.run(
|
|
["node", "-e", script],
|
|
cwd=".",
|
|
text=True,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
assert result.returncode == 0, result.stderr
|
|
assert [item["url"] for item in json.loads(result.stdout)] == [
|
|
"/item-6",
|
|
"/item-5",
|
|
"/item-4",
|
|
"/item-3",
|
|
"/item-2",
|
|
]
|
|
assert json.loads(result.stdout)[0]["display_name"] == "Recent Datasette releases"
|