From 8d2fa1c1843f820d59bd9089c2a6b2d4902b0d4f Mon Sep 17 00:00:00 2001
From: David Taylor <david@taylorhq.com>
Date: Tue, 21 Feb 2023 14:37:15 +0000
Subject: [PATCH] DEV: Add native class shims for `on`/`observes` decorators

---
 .../addon/controllers/admin-badges/show.js    |  2 +-
 .../addon/utils/decorators.js                 | 45 ++++++++++--
 .../tests/unit/utils/decorators-test.js       | 72 +++++++++++++++++++
 3 files changed, 113 insertions(+), 6 deletions(-)

diff --git a/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js b/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js
index 382cee16de8..cae5e0dddbd 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js
@@ -1,5 +1,5 @@
 import Controller, { inject as controller } from "@ember/controller";
-import { observes } from "discourse-common/utils/decorators";
+import { observes } from "@ember-decorators/object";
 import I18n from "I18n";
 import { bufferedProperty } from "discourse/mixins/buffered-content";
 import { popupAjaxError } from "discourse/lib/ajax-error";
diff --git a/app/assets/javascripts/discourse-common/addon/utils/decorators.js b/app/assets/javascripts/discourse-common/addon/utils/decorators.js
index 1320285db11..e4cd53ca62e 100644
--- a/app/assets/javascripts/discourse-common/addon/utils/decorators.js
+++ b/app/assets/javascripts/discourse-common/addon/utils/decorators.js
@@ -1,4 +1,9 @@
 import { on as emberOn } from "@ember/object/evented";
+import {
+  observes as emberObservesDecorator,
+  on as emberOnDecorator,
+} from "@ember-decorators/object";
+
 import { observer } from "@ember/object";
 import {
   alias as EmberAlias,
@@ -37,6 +42,8 @@ import handleDescriptor from "discourse-common/utils/handle-descriptor";
 import isDescriptor from "discourse-common/utils/is-descriptor";
 import macroAlias from "discourse-common/utils/macro-alias";
 import discourseDebounce from "discourse-common/lib/debounce";
+import CoreObject from "@ember/object/core";
+import deprecated from "discourse-common/lib/deprecated";
 
 export default function discourseComputedDecorator(...params) {
   // determine if user called as @discourseComputed('blah', 'blah') or @discourseComputed
@@ -112,11 +119,39 @@ export function debounce(delay, immediate = false) {
   };
 }
 
-export const on = decoratorAlias(emberOn, "Can not `on` without event names");
-export const observes = decoratorAlias(
-  observer,
-  "Can not `observe` without property names"
-);
+export function on(...onParams) {
+  return function (target) {
+    if (target instanceof CoreObject) {
+      deprecated(
+        `Using 'on' from 'discourse-common/utils/decorators' as a class property decorator is deprecated. You should import it from '@ember-decorators/object' instead.`,
+        { id: "discourse.utils-decorators-on", from: "3.1.0.beta2" }
+      );
+      return emberOnDecorator(...onParams)(...arguments);
+    } else {
+      return decoratorAlias(
+        emberOn,
+        "Can not `on` without event names"
+      )(...onParams)(...arguments);
+    }
+  };
+}
+
+export function observes(...observeParams) {
+  return function (target) {
+    if (target instanceof CoreObject) {
+      deprecated(
+        `Using 'observes' from 'discourse-common/utils/decorators' as a class property decorator is deprecated. You should import it from '@ember-decorators/object' instead.`,
+        { id: "discourse.utils-decorators-observes", from: "3.1.0.beta2" }
+      );
+      return emberObservesDecorator(...observeParams)(...arguments);
+    } else {
+      return decoratorAlias(
+        observer,
+        "Can not `observe` without property names"
+      )(...observeParams)(...arguments);
+    }
+  };
+}
 
 export const alias = macroAlias(EmberAlias);
 export const and = macroAlias(EmberAnd);
diff --git a/app/assets/javascripts/discourse/tests/unit/utils/decorators-test.js b/app/assets/javascripts/discourse/tests/unit/utils/decorators-test.js
index 1c07f7dcf52..4426bd29166 100644
--- a/app/assets/javascripts/discourse/tests/unit/utils/decorators-test.js
+++ b/app/assets/javascripts/discourse/tests/unit/utils/decorators-test.js
@@ -6,10 +6,12 @@ import discourseComputed, {
   afterRender,
   debounce,
   observes,
+  on,
 } from "discourse-common/utils/decorators";
 import { exists } from "discourse/tests/helpers/qunit-helpers";
 import { hbs } from "ember-cli-htmlbars";
 import EmberObject from "@ember/object";
+import { withSilencedDeprecations } from "discourse-common/lib/deprecated";
 
 const fooComponent = Component.extend({
   classNames: ["foo-component"],
@@ -175,4 +177,74 @@ module("Unit | Utils | decorators", function (hooks) {
 
     assert.strictEqual(stub.otherCounter, 1);
   });
+
+  test("@observes works via .extend and native class syntax", async function (assert) {
+    let NativeClassWithObserver;
+    withSilencedDeprecations("discourse.utils-decorators-observes", () => {
+      NativeClassWithObserver = class extends EmberObject {
+        counter = 0;
+        @observes("value")
+        incrementCounter() {
+          this.set("counter", this.counter + 1);
+        }
+      };
+    });
+
+    const ExtendWithObserver = EmberObject.extend({
+      counter: 0,
+      @observes("value")
+      incrementCounter() {
+        this.set("counter", this.counter + 1);
+      },
+    });
+
+    const nativeClassTest = NativeClassWithObserver.create();
+    nativeClassTest.set("value", "one");
+    await settled();
+    nativeClassTest.set("value", "two");
+    await settled();
+    assert.strictEqual(
+      nativeClassTest.counter,
+      2,
+      "observer triggered for native class"
+    );
+
+    const extendTest = ExtendWithObserver.create();
+    extendTest.set("value", "one");
+    await settled();
+    extendTest.set("value", "two");
+    await settled();
+    assert.strictEqual(extendTest.counter, 2, "observer triggered for .extend");
+  });
+
+  test("@on works via .extend and native class syntax", async function (assert) {
+    let NativeClassWithOn;
+    withSilencedDeprecations("discourse.utils-decorators-on", () => {
+      NativeClassWithOn = class extends EmberObject {
+        counter = 0;
+        @on("init")
+        incrementCounter() {
+          this.set("counter", this.counter + 1);
+        }
+      };
+    });
+
+    const ExtendWithOn = EmberObject.extend({
+      counter: 0,
+      @on("init")
+      incrementCounter() {
+        this.set("counter", this.counter + 1);
+      },
+    });
+
+    const nativeClassTest = NativeClassWithOn.create();
+    assert.strictEqual(
+      nativeClassTest.counter,
+      1,
+      "on triggered for native class"
+    );
+
+    const extendTest = ExtendWithOn.create();
+    assert.strictEqual(extendTest.counter, 1, "on triggered for .extend");
+  });
 });