Fix local tests to use web_test_runner

These have been broken for a while since Gerrit removed karma
test runner.

Bug: 415387031
Change-Id: I2ef699a234af0bb871bfb233b738974c0cec8975
diff --git a/web/BUILD b/web/BUILD
index 83908cf..7888760 100644
--- a/web/BUILD
+++ b/web/BUILD
@@ -1,6 +1,6 @@
 load("@npm//@bazel/rollup:index.bzl", "rollup_bundle")
 load("//tools/js:eslint.bzl", "plugin_eslint")
-load("//tools/bzl:js.bzl", "gerrit_js_bundle", "karma_test")
+load("//tools/bzl:js.bzl", "gerrit_js_bundle", "web_test_runner")
 load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
 load("//tools/bzl:plugin.bzl", "gerrit_plugin")
 
@@ -47,10 +47,14 @@
     ],
 )
 
-karma_test(
-    name = "karma_test",
-    srcs = ["karma_test.sh"],
-    data = [":code_coverage_ts-tests"],
+web_test_runner(
+    name = "web_test_runner",
+    srcs = ["web_test_runner.sh"],
+    data = [
+        ":tsconfig",
+        ":code_coverage_ts-tests",
+        "@plugins_npm//:node_modules",
+    ],
 )
 
 plugin_eslint()
diff --git a/web/Makefile b/web/Makefile
index 4616c13..0509c96 100644
--- a/web/Makefile
+++ b/web/Makefile
@@ -5,7 +5,7 @@
 .PHONY: test
 
 test:
-	bazel test --test_output=all //plugins/code-coverage/web:karma_test
+	bazel test --test_output=all //plugins/code-coverage/web:web_test_runner
 
 build:
 	bazel build //plugins/code-coverage/web:code_coverage && \
diff --git a/web/coverage-percentage-views.ts b/web/coverage-percentage-views.ts
index 703ef88..5da398e 100644
--- a/web/coverage-percentage-views.ts
+++ b/web/coverage-percentage-views.ts
@@ -5,7 +5,7 @@
  */
 
 import {css, html, LitElement, PropertyValues} from 'lit';
-import {customElement, property} from 'lit/decorators';
+import {customElement, property} from 'lit/decorators.js';
 import {PercentageData} from './coverage';
 
 const COMMON_CSS = css`
diff --git a/web/coverage_test.ts b/web/coverage_test.ts
index ca2f27f..c0f44d3 100644
--- a/web/coverage_test.ts
+++ b/web/coverage_test.ts
@@ -7,6 +7,8 @@
 
 import {assert} from '@open-wc/testing';
 
+import './test-setup';
+
 import {PluginApi} from '@gerritcodereview/typescript-api/plugin';
 
 import {
diff --git a/web/karma_test.sh b/web/karma_test.sh
deleted file mode 100755
index 9c489e9..0000000
--- a/web/karma_test.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-
-set -euo pipefail
-./$1 start $2 --single-run \
-  --root 'plugins/code-coverage/web/_bazel_ts_out_tests/' \
-  --test-files '*_test.js' \
-  --browsers ${3:-ChromeHeadless}
diff --git a/web/percentage_views_test.ts b/web/percentage_views_test.ts
index 0a852e1..c5dc596 100644
--- a/web/percentage_views_test.ts
+++ b/web/percentage_views_test.ts
@@ -5,7 +5,9 @@
  * found in the LICENSE file.
  */
 
-import {assert} from '@open-wc/testing';
+import {assert, fixture, html} from '@open-wc/testing';
+
+import './test-setup';
 
 import './coverage';
 
@@ -24,28 +26,26 @@
   let acv: AbsoluteContentView;
   let icv: IncrementalContentView;
 
-  setup(() => {
-    ahv = document.createElement('absolute-header-view') as AbsoluteHeaderView;
-    document.body.appendChild(ahv);
-    ihv = document.createElement(
-      'incremental-header-view'
-    ) as IncrementalHeaderView;
-    document.body.appendChild(ihv);
-    acv = document.createElement(
-      'absolute-content-view'
-    ) as AbsoluteContentView;
-    document.body.appendChild(acv);
-    icv = document.createElement(
-      'incremental-content-view'
-    ) as IncrementalContentView;
-    document.body.appendChild(icv);
+  setup(async () => {
+    assert.isDefined(AbsoluteHeaderView);
+    assert.isDefined(IncrementalHeaderView);
+    assert.isDefined(AbsoluteContentView);
+    assert.isDefined(IncrementalContentView);
+    ahv = await fixture<AbsoluteHeaderView>(
+      html`<absolute-header-view></absolute-header-view>`
+    );
+    ihv = await fixture<IncrementalHeaderView>(
+      html`<incremental-header-view></incremental-header-view>`
+    );
+    acv = await fixture<AbsoluteContentView>(
+      html`<absolute-content-view></absolute-content-view>`
+    );
+    icv = await fixture<IncrementalContentView>(
+      html`<incremental-content-view></incremental-content-view>`
+    );
   });
 
   teardown(() => {
-    document.body.removeChild(ahv);
-    document.body.removeChild(ihv);
-    document.body.removeChild(acv);
-    document.body.removeChild(icv);
   });
 
   test('absolute header view', async () => {
diff --git a/web/plugin.ts b/web/plugin.ts
index 85b5217..10f8cbe 100644
--- a/web/plugin.ts
+++ b/web/plugin.ts
@@ -11,7 +11,7 @@
 import {ChangeData} from '@gerritcodereview/typescript-api/checks';
 import {EventType, PluginApi} from '@gerritcodereview/typescript-api/plugin';
 
-window.Gerrit.install((plugin: PluginApi) => {
+window.Gerrit?.install((plugin: PluginApi) => {
   const coverageClient = new CoverageClient(plugin);
 
   // Displays coverage metrics in file diff view.
diff --git a/web/test-setup.ts b/web/test-setup.ts
new file mode 100644
index 0000000..9062498
--- /dev/null
+++ b/web/test-setup.ts
@@ -0,0 +1,28 @@
+// Copyright 2025 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {css} from 'lit';
+import sinon from 'sinon';
+
+declare global {
+  interface Window {
+    sinon: typeof sinon;
+  }
+}
+
+window.sinon = sinon;
+
+window.Gerrit = {
+  install: () => {},
+  styles: {
+    font: css``,
+    form: css``,
+    icon: css``,
+    menuPage: css``,
+    spinner: css``,
+    subPage: css``,
+    table: css``,
+    modal: css``,
+  },
+};
diff --git a/web/web_test_runner.sh b/web/web_test_runner.sh
new file mode 100755
index 0000000..15e094a
--- /dev/null
+++ b/web/web_test_runner.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+set -euo pipefail
+
+./$1 --config $2 \
+  --dir 'plugins/code-coverage/web/_bazel_ts_out_tests' \
+  --test-files 'plugins/code-coverage/web/_bazel_ts_out_tests/*_test.js' \
+  --ts-config="plugins/code-coverage/web/tsconfig.json"