Merge "Introduce a diff model"
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts b/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts
new file mode 100644
index 0000000..8fbda14
--- /dev/null
+++ b/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts
@@ -0,0 +1,47 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import {Observable} from 'rxjs';
+import {filter} from 'rxjs/operators';
+import {
+  DiffInfo,
+  DiffPreferencesInfo,
+  RenderPreferences,
+} from '../../../api/diff';
+import {define} from '../../../models/dependency';
+import {Model} from '../../../models/model';
+import {isDefined} from '../../../types/types';
+import {select} from '../../../utils/observable-util';
+
+export interface DiffState {
+  diff: DiffInfo;
+  path?: string;
+  renderPrefs: RenderPreferences;
+  diffPrefs: DiffPreferencesInfo;
+}
+
+export const diffModelToken = define<DiffModel>('diff-model');
+
+export class DiffModel extends Model<DiffState | undefined> {
+  readonly diff$: Observable<DiffInfo> = select(
+    this.state$.pipe(filter(isDefined)),
+    diffState => diffState.diff
+  );
+
+  readonly path$: Observable<string | undefined> = select(
+    this.state$.pipe(filter(isDefined)),
+    diffState => diffState.path
+  );
+
+  readonly renderPrefs$: Observable<RenderPreferences> = select(
+    this.state$.pipe(filter(isDefined)),
+    diffState => diffState.renderPrefs
+  );
+
+  readonly diffPrefs$: Observable<DiffPreferencesInfo> = select(
+    this.state$.pipe(filter(isDefined)),
+    diffState => diffState.diffPrefs
+  );
+}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
index 54bc46d..b9a01ce 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
@@ -69,6 +69,8 @@
 import {classMap} from 'lit/directives/class-map.js';
 import {iconStyles} from '../../../styles/gr-icon-styles';
 import {expandFileMode} from '../../../utils/file-util';
+import {DiffModel, diffModelToken} from '../gr-diff-model/gr-diff-model';
+import {provide} from '../../../models/dependency';
 
 const NO_NEWLINE_LEFT = 'No newline at end of left file.';
 const NO_NEWLINE_RIGHT = 'No newline at end of right file.';
@@ -138,7 +140,7 @@
   prefs?: DiffPreferencesInfo;
 
   @property({type: Object})
-  renderPrefs?: RenderPreferences;
+  renderPrefs: RenderPreferences = {};
 
   @property({type: Boolean})
   isImageDiff?: boolean;
@@ -265,6 +267,8 @@
   // Private but used in tests.
   diffBuilder = new GrDiffBuilderElement();
 
+  private diffModel = new DiffModel(undefined);
+
   static override get styles() {
     return [
       iconStyles,
@@ -984,6 +988,7 @@
 
   constructor() {
     super();
+    provide(this, diffModelToken, () => this.diffModel);
     this.addEventListener('create-range-comment', (e: Event) =>
       this.handleCreateRangeComment(e as CustomEvent)
     );
@@ -1440,6 +1445,7 @@
 
   private prefsChanged() {
     if (!this.prefs) return;
+    this.diffModel.updateState({diffPrefs: this.prefs});
 
     this.blame = null;
     this.updatePreferenceStyles();
@@ -1510,7 +1516,7 @@
   }
 
   private renderPrefsChanged() {
-    if (!this.renderPrefs) return;
+    this.diffModel.updateState({renderPrefs: this.renderPrefs});
     if (this.renderPrefs.hide_left_side) {
       this.classList.add('no-left');
     }
@@ -1577,7 +1583,7 @@
   // Private but used in tests.
   async renderDiffTable() {
     this.unobserveNodes();
-    if (!this.prefs) {
+    if (!this.diff || !this.prefs) {
       fireEvent(this, 'render');
       return;
     }
@@ -1596,8 +1602,15 @@
 
     const keyLocations = this.computeKeyLocations();
 
+    this.diffModel.setState({
+      diff: this.diff,
+      path: this.path,
+      renderPrefs: this.renderPrefs,
+      diffPrefs: this.prefs,
+    });
+
     // TODO: Setting tons of public properties like this is obviously a code
-    // smell. We are planning to introduce a diff model for managing all this
+    // smell. We are introducing a diff model for managing all this
     // data. Then diff builder will only need access to that model.
     this.diffBuilder.prefs = this.getBypassPrefs();
     this.diffBuilder.renderPrefs = this.renderPrefs;
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
index 2855ae6..4adb1cf 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
@@ -3956,6 +3956,7 @@
 
     setup(async () => {
       element.prefs = {...MINIMAL_PREFS};
+      element.diff = createDiff();
       renderStub = sinon.stub(element.diffBuilder, 'render');
       await element.updateComplete;
     });