content: move clipboard.js code to content/ts

Migrates CopyToClipboardController to TypeScript
and adds a unit test.

For golang/go#43359
For golang/go#43361

Change-Id: I2e1179aa7cecb0e280a57d420ee2f885c4d6db3c
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/280893
Reviewed-by: Julie Qiu <julie@golang.org>
Trust: Jamal Carvalho <jamal@golang.org>
diff --git a/content/lib/ts/clipboard.test.ts b/content/lib/ts/clipboard.test.ts
new file mode 100644
index 0000000..28ef5ad
--- /dev/null
+++ b/content/lib/ts/clipboard.test.ts
@@ -0,0 +1,42 @@
+/*!
+ * @license
+ * Copyright 2020 The Go 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 { CopyToClipboardController } from './clipboard';
+
+describe('CopyToClipboardController', () => {
+  let button: HTMLButtonElement;
+  let controller: CopyToClipboardController;
+  const dataToCopy = 'Hello, world!';
+
+  beforeEach(() => {
+    document.body.innerHTML = `
+      <div>
+        <button class="js-copyToClipboard" data-to-copy="${dataToCopy}"></button>
+      </div>
+    `;
+    button = document.querySelector<HTMLButtonElement>('.js-copyToClipboard');
+    controller = new CopyToClipboardController(button);
+  });
+
+  afterEach(() => {
+    document.body.innerHTML = '';
+  });
+
+  it('copys text when clicked', () => {
+    Object.assign(navigator, { clipboard: { writeText: () => Promise.resolve() } });
+    jest.spyOn(navigator.clipboard, 'writeText');
+    button.click();
+    expect(navigator.clipboard.writeText).toHaveBeenCalledWith(dataToCopy);
+  });
+
+  it('shows error when clicked if clipboard is undefined', () => {
+    Object.assign(navigator, { clipboard: undefined });
+    jest.spyOn(controller, 'showTooltipText');
+    button.click();
+    expect(controller.showTooltipText).toHaveBeenCalledWith('Unable to copy', 1000);
+  });
+});
diff --git a/content/lib/ts/clipboard.ts b/content/lib/ts/clipboard.ts
new file mode 100644
index 0000000..51a43e6
--- /dev/null
+++ b/content/lib/ts/clipboard.ts
@@ -0,0 +1,58 @@
+/*!
+ * @license
+ * Copyright 2020 The Go Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/**
+ * This class decorates an element to copy arbitrary data attached via a data-
+ * attribute to the clipboard.
+ */
+export class CopyToClipboardController {
+  private _el: HTMLButtonElement;
+  /**
+   * The data to be copied to the clipboard.
+   */
+  private _data: string;
+
+  /**
+   * @param el The element that will trigger copying text to the clipboard. The text is
+   * expected to be within its data-to-copy attribute.
+   */
+  constructor(el: HTMLButtonElement) {
+    this._el = el;
+    this._data = el.dataset['toCopy'] ?? '';
+    el.addEventListener('click', e => this.handleCopyClick(e));
+  }
+
+  /**
+   * Handles when the primary element is clicked.
+   */
+  handleCopyClick(e: MouseEvent): void {
+    e.preventDefault();
+    const TOOLTIP_SHOW_DURATION_MS = 1000;
+
+    // This API is not available on iOS.
+    if (!navigator.clipboard) {
+      this.showTooltipText('Unable to copy', TOOLTIP_SHOW_DURATION_MS);
+      return;
+    }
+    navigator.clipboard
+      .writeText(this._data)
+      .then(() => {
+        this.showTooltipText('Copied!', TOOLTIP_SHOW_DURATION_MS);
+      })
+      .catch(() => {
+        this.showTooltipText('Unable to copy', TOOLTIP_SHOW_DURATION_MS);
+      });
+  }
+
+  /**
+   * Shows the given text in a tooltip for a specified amount of time, in milliseconds.
+   */
+  showTooltipText(text: string, durationMs: number): void {
+    this._el.setAttribute('data-tooltip', text);
+    setTimeout(() => this._el.setAttribute('data-tooltip', ''), durationMs);
+  }
+}
diff --git a/content/static/js/clipboard.js b/content/static/js/clipboard.js
index c9b7e35..0773009 100644
--- a/content/static/js/clipboard.js
+++ b/content/static/js/clipboard.js
@@ -1,47 +1,18 @@
-/**
+/*!
  * @license
- * Copyright 2019-2020 The Go Authors. All rights reserved.
+ * Copyright 2020 The Go Authors. All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the LICENSE file.
  */
-
-/**
- * This class decorates an element to copy arbitrary data attached via a data-
- * attribute to the clipboard.
- */
-class CopyToClipboardController {
-  /**
-   * The element that will trigger copying text to the clipboard. The text is
-   * expected to be within its data-to-copy attribute.
-   * @param {!Element} el
-   */
+export class CopyToClipboardController {
   constructor(el) {
-    /**
-     * @type {!Element}
-     * @private
-     */
     this._el = el;
-
-    /**
-     * The data to be copied to the clipboard.
-     * @type {string}
-     * @private
-     */
-    this._data = el.dataset['toCopy'];
-
-    el.addEventListener('click', e => this.handleCopyClick(/** @type {!Event} */ (e)));
+    this._data = el.dataset['toCopy'] ?? '';
+    el.addEventListener('click', e => this.handleCopyClick(e));
   }
-
-  /**
-   * Handles when the primary element is clicked.
-   * @param {!Event} e
-   * @private
-   */
   handleCopyClick(e) {
     e.preventDefault();
     const TOOLTIP_SHOW_DURATION_MS = 1000;
-
-    // This API is not available on iOS.
     if (!navigator.clipboard) {
       this.showTooltipText('Unable to copy', TOOLTIP_SHOW_DURATION_MS);
       return;
@@ -55,15 +26,9 @@
         this.showTooltipText('Unable to copy', TOOLTIP_SHOW_DURATION_MS);
       });
   }
-
-  /**
-   * Shows the given text in a tooltip for a specified amount of time, in milliseconds.
-   * @param {string} text
-   * @param {number} durationMs
-   * @private
-   */
   showTooltipText(text, durationMs) {
     this._el.setAttribute('data-tooltip', text);
     setTimeout(() => this._el.setAttribute('data-tooltip', ''), durationMs);
   }
 }
+//# sourceMappingURL=clipboard.js.map
diff --git a/content/static/js/clipboard.js.map b/content/static/js/clipboard.js.map
new file mode 100644
index 0000000..245c951
--- /dev/null
+++ b/content/static/js/clipboard.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../lib/ts/clipboard.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,OAAO,yBAAyB;IAWpC,YAAY,EAAqB;QAC/B,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;QACd,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxC,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IAKD,eAAe,CAAC,CAAa;QAC3B,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,MAAM,wBAAwB,GAAG,IAAI,CAAC;QAGtC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE;YACxB,IAAI,CAAC,eAAe,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;YACjE,OAAO;SACR;QACD,SAAS,CAAC,SAAS;aAChB,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC;aACrB,IAAI,CAAC,GAAG,EAAE;YACT,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,wBAAwB,CAAC,CAAC;QAC5D,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,EAAE;YACV,IAAI,CAAC,eAAe,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;IACP,CAAC;IAKD,eAAe,CAAC,IAAY,EAAE,UAAkB;QAC9C,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;QAC5C,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC;IAC1E,CAAC;CACF"}
\ No newline at end of file
diff --git a/content/static/js/unit.js b/content/static/js/unit.js
index 23cdcd5..492f27a 100644
--- a/content/static/js/unit.js
+++ b/content/static/js/unit.js
@@ -6,6 +6,7 @@
  */
 
 import { AccordionController } from './accordion.js';
+import { CopyToClipboardController } from './clipboard.js';
 import './toggle-tip.js';
 
 /**
@@ -18,6 +19,14 @@
 }
 
 /**
+ * Instantiates CopyToClipboardController controller copy buttons
+ * on the unit page.
+ */
+document.querySelectorAll('.js-copyToClipboard').forEach(el => {
+  new CopyToClipboardController(el);
+});
+
+/**
  * Event handlers for expanding and collapsing the readme section.
  */
 const readme = document.querySelector('.js-readme');
diff --git a/content/static/js/unit_fixed_header.js b/content/static/js/unit_fixed_header.js
index b28691c..a0b2ee1 100644
--- a/content/static/js/unit_fixed_header.js
+++ b/content/static/js/unit_fixed_header.js
@@ -76,73 +76,6 @@
   document.querySelector('.js-fixedHeader')
 );
 
-/**
- * This class decorates an element to copy arbitrary data attached via a data-
- * attribute to the clipboard.
- */
-class CopyToClipboardController {
-  /**
-   * The element that will trigger copying text to the clipboard. The text is
-   * expected to be within its data-to-copy attribute.
-   * @param {!Element} el
-   */
-  constructor(el) {
-    /**
-     * @type {!Element}
-     * @private
-     */
-    this._el = el;
-
-    /**
-     * The data to be copied to the clipboard.
-     * @type {string}
-     * @private
-     */
-    this._data = el.dataset['toCopy'];
-
-    el.addEventListener('click', e => this.handleCopyClick(/** @type {!Event} */ (e)));
-  }
-
-  /**
-   * Handles when the primary element is clicked.
-   * @param {!Event} e
-   * @private
-   */
-  handleCopyClick(e) {
-    e.preventDefault();
-    const TOOLTIP_SHOW_DURATION_MS = 1000;
-
-    // This API is not available on iOS.
-    if (!navigator.clipboard) {
-      this.showTooltipText('Unable to copy', TOOLTIP_SHOW_DURATION_MS);
-      return;
-    }
-    navigator.clipboard
-      .writeText(this._data)
-      .then(() => {
-        this.showTooltipText('Copied!', TOOLTIP_SHOW_DURATION_MS);
-      })
-      .catch(() => {
-        this.showTooltipText('Unable to copy', TOOLTIP_SHOW_DURATION_MS);
-      });
-  }
-
-  /**
-   * Shows the given text in a tooltip for a specified amount of time, in milliseconds.
-   * @param {string} text
-   * @param {number} durationMs
-   * @private
-   */
-  showTooltipText(text, durationMs) {
-    this._el.setAttribute('data-tooltip', text);
-    setTimeout(() => this._el.setAttribute('data-tooltip', ''), durationMs);
-  }
-}
-
-document.querySelectorAll('.js-copyToClipboard').forEach(el => {
-  new CopyToClipboardController(el);
-});
-
 const overflowSelect = document.querySelector('.js-overflowSelect');
 if (overflowSelect) {
   overflowSelect.addEventListener('change', e => {