Archived
Add pre-commit verification hook
This commit is contained in:
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
echo "Running pre-commit checks: npm run verify"
|
||||
npm run verify
|
||||
+2
-1
@@ -36,7 +36,8 @@
|
||||
"prepack": "npm run build",
|
||||
"pack:dry": "npm pack --dry-run",
|
||||
"prepublishOnly": "npm run verify",
|
||||
"publish:npm": "npm publish --access public"
|
||||
"publish:npm": "npm publish --access public",
|
||||
"prepare": "node scripts/install-git-hooks.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
|
||||
if (!existsSync('.git')) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
execFileSync('git', ['config', 'core.hooksPath', '.githooks'], { stdio: 'inherit' });
|
||||
console.log('Configured git hooks path: .githooks');
|
||||
} catch (error) {
|
||||
console.warn('Could not configure git hooks path. Run: git config core.hooksPath .githooks');
|
||||
process.exitCode = 0;
|
||||
}
|
||||
@@ -282,11 +282,11 @@ export class PiWebApp extends LitElement {
|
||||
selectedStagedDiff: this.state.selectedStagedDiff,
|
||||
gitStale: this.state.gitStale,
|
||||
activeTerminalCount: this.state.activeTerminalCount,
|
||||
onRefreshFiles: () => this.files.refreshFiles(),
|
||||
onExpandDir: (path: string) => this.files.expandDir(path),
|
||||
onSelectFile: (path: string) => this.files.selectFile(path),
|
||||
onRefreshGit: () => this.git.refreshGit(),
|
||||
onSelectDiff: (path: string) => this.git.selectDiff(path),
|
||||
onRefreshFiles: () => { void this.files.refreshFiles(); },
|
||||
onExpandDir: (path: string) => { void this.files.expandDir(path); },
|
||||
onSelectFile: (path: string) => { void this.files.selectFile(path); },
|
||||
onRefreshGit: () => { void this.git.refreshGit(); },
|
||||
onSelectDiff: (path: string) => { void this.git.selectDiff(path); },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,21 +4,21 @@ import { activateSelectableRow, activateSelectableRowFromKeyboard } from "./sele
|
||||
describe("selectable row activation", () => {
|
||||
it("activates rows from non-interactive click targets", () => {
|
||||
const action = vi.fn();
|
||||
activateSelectableRow(eventWithPath({ matches: () => false }), action);
|
||||
activateSelectableRow(eventWithPath(matchTarget(() => false)), action);
|
||||
expect(action).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("preserves contributed links and other interactive elements", () => {
|
||||
const action = vi.fn();
|
||||
activateSelectableRow(eventWithPath({ matches: (selector: string) => selector.includes("a[href]") }), action);
|
||||
activateSelectableRow(eventWithPath(matchTarget((selector: string) => selector.includes("a[href]"))), action);
|
||||
expect(action).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("activates rows from Enter and Space", () => {
|
||||
const enterAction = vi.fn();
|
||||
const spaceAction = vi.fn();
|
||||
const enter = keyboardEventWithPath("Enter", { matches: () => false });
|
||||
const space = keyboardEventWithPath(" ", { matches: () => false });
|
||||
const enter = keyboardEventWithPath("Enter", matchTarget(() => false));
|
||||
const space = keyboardEventWithPath(" ", matchTarget(() => false));
|
||||
|
||||
activateSelectableRowFromKeyboard(enter, enterAction);
|
||||
activateSelectableRowFromKeyboard(space, spaceAction);
|
||||
@@ -31,7 +31,7 @@ describe("selectable row activation", () => {
|
||||
|
||||
it("does not activate rows from keyboard events inside interactive elements", () => {
|
||||
const action = vi.fn();
|
||||
const event = keyboardEventWithPath("Enter", { matches: (selector: string) => selector.includes("button") });
|
||||
const event = keyboardEventWithPath("Enter", matchTarget((selector: string) => selector.includes("button")));
|
||||
|
||||
activateSelectableRowFromKeyboard(event, action);
|
||||
|
||||
@@ -40,10 +40,18 @@ describe("selectable row activation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function eventWithPath(target: Pick<Element, "matches">): MouseEvent {
|
||||
return { composedPath: () => [target] } as unknown as MouseEvent;
|
||||
type EventWithPath = Pick<Event, "composedPath">;
|
||||
type KeyboardEventWithPath = EventWithPath & Pick<KeyboardEvent, "key" | "preventDefault">;
|
||||
type MatchTarget = EventTarget & Pick<Element, "matches">;
|
||||
|
||||
function matchTarget(matches: Element["matches"]): MatchTarget {
|
||||
return Object.assign(new EventTarget(), { matches });
|
||||
}
|
||||
|
||||
function keyboardEventWithPath(key: string, target: Pick<Element, "matches">): KeyboardEvent & { preventDefault: ReturnType<typeof vi.fn> } {
|
||||
return { key, preventDefault: vi.fn(), composedPath: () => [target] } as unknown as KeyboardEvent & { preventDefault: ReturnType<typeof vi.fn> };
|
||||
function eventWithPath(target: MatchTarget): EventWithPath {
|
||||
return { composedPath: () => [target] };
|
||||
}
|
||||
|
||||
function keyboardEventWithPath(key: string, target: MatchTarget): KeyboardEventWithPath {
|
||||
return { key, preventDefault: vi.fn<() => void>(), composedPath: () => [target] };
|
||||
}
|
||||
|
||||
@@ -10,21 +10,26 @@ const interactiveSelector = [
|
||||
"[contenteditable='true']",
|
||||
].join(",");
|
||||
|
||||
export function isFromInteractiveElement(event: Event): boolean {
|
||||
return event.composedPath().some((target) => isElementLike(target) && target.matches(interactiveSelector));
|
||||
type ComposedPathEvent = Pick<Event, "composedPath">;
|
||||
type SelectableKeyboardEvent = ComposedPathEvent & Pick<KeyboardEvent, "key" | "preventDefault">;
|
||||
|
||||
export function isFromInteractiveElement(event: ComposedPathEvent): boolean {
|
||||
return event.composedPath().some((target) => targetMatches(target, interactiveSelector));
|
||||
}
|
||||
|
||||
function isElementLike(target: EventTarget): target is Element {
|
||||
if (typeof Element !== "undefined") return target instanceof Element;
|
||||
return typeof (target as Partial<Element>).matches === "function";
|
||||
function targetMatches(target: EventTarget, selector: string): boolean {
|
||||
if (typeof Element !== "undefined" && target instanceof Element) return target.matches(selector);
|
||||
if (!("matches" in target)) return false;
|
||||
const { matches } = target;
|
||||
return typeof matches === "function" && matches.call(target, selector) === true;
|
||||
}
|
||||
|
||||
export function activateSelectableRow(event: MouseEvent, action: () => void): void {
|
||||
export function activateSelectableRow(event: ComposedPathEvent, action: () => void): void {
|
||||
if (isFromInteractiveElement(event)) return;
|
||||
action();
|
||||
}
|
||||
|
||||
export function activateSelectableRowFromKeyboard(event: KeyboardEvent, action: () => void): void {
|
||||
export function activateSelectableRowFromKeyboard(event: SelectableKeyboardEvent, action: () => void): void {
|
||||
if (event.key !== "Enter" && event.key !== " ") return;
|
||||
if (isFromInteractiveElement(event)) return;
|
||||
event.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user