refactor: add ScriptRunner class

Change-Id: I118adb9ec338e88b40321208b84228886bb6b590
diff --git a/src/common/architecture/scripts/ScriptRunner.test.ts b/src/common/architecture/scripts/ScriptRunner.test.ts
new file mode 100644
index 0000000..7435c8e
--- /dev/null
+++ b/src/common/architecture/scripts/ScriptRunner.test.ts
@@ -0,0 +1,83 @@
+import Script, {
+  ScriptEnvironment,
+  ScriptPage,
+  ScriptRunPhase,
+} from './Script';
+import { beforeEach, expect, it, jest } from '@jest/globals';
+import ScriptRunner from './ScriptRunner';
+
+interface FakeScriptOptions {
+  id: string;
+  priority: number;
+}
+
+class FakeScript extends Script {
+  id: string;
+  priority: number;
+  page: ScriptPage.CommunityConsole;
+  environment: ScriptEnvironment.ContentScript;
+  runPhase: ScriptRunPhase.Main;
+
+  constructor(options: FakeScriptOptions) {
+    super();
+    this.id = options.id;
+    this.priority = options.priority;
+  }
+
+  execute() {}
+}
+
+const fakeScriptMock = jest
+  .spyOn(FakeScript.prototype, 'execute')
+  .mockImplementation(function() {
+    return (this as FakeScript).id;
+  });
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+it('scripts run in the correct order based on priority', () => {
+  const scriptsConfig = [
+    {
+      script: new FakeScript({
+        id: '1',
+        priority: 1,
+      }),
+      expectedRunPosition: 2,
+    },
+    {
+      script: new FakeScript({
+        id: '2',
+        priority: 1000,
+      }),
+      expectedRunPosition: 3,
+    },
+    {
+      script: new FakeScript({
+        id: '3',
+        priority: 0,
+      }),
+      expectedRunPosition: 1,
+    },
+    {
+      script: new FakeScript({
+        id: '4',
+        priority: 2 ** 31,
+      }),
+      expectedRunPosition: 4,
+    },
+  ];
+
+  const scriptRunner = new ScriptRunner();
+  scriptRunner.add(...scriptsConfig.map((config) => config.script));
+  scriptRunner.run();
+
+  expect(fakeScriptMock).toHaveBeenCalledTimes(scriptsConfig.length);
+  for (const config of scriptsConfig) {
+    expect(fakeScriptMock).toHaveNthReturnedWith(
+      config.expectedRunPosition,
+      config.script.id,
+    );
+  }
+});
diff --git a/src/common/architecture/scripts/ScriptRunner.ts b/src/common/architecture/scripts/ScriptRunner.ts
new file mode 100644
index 0000000..4a2ed2d
--- /dev/null
+++ b/src/common/architecture/scripts/ScriptRunner.ts
@@ -0,0 +1,20 @@
+import Script from './Script';
+
+export default class ScriptRunner {
+  private scripts: Script[] = [];
+
+  add(...scripts: Script[]) {
+    this.scripts.push(...scripts);
+  }
+
+  run() {
+    this.scripts.sort((a, b) => {
+      if (a.priority < b.priority) return -1;
+      if (a.priority > b.priority) return 1;
+      return 0;
+    });
+    for (const script of this.scripts) {
+      script.execute();
+    }
+  }
+}