Unverified Commit 86382c83 authored by Kyle Keating's avatar Kyle Keating Committed by GitHub

Improve typechecking for util/ and services/ (#2315)

parent 360d308a
...@@ -34,7 +34,6 @@ export const events = { ...@@ -34,7 +34,6 @@ export const events = {
* behavior across different environments in which the client is used. * behavior across different environments in which the client is used.
* *
* @param {Window} win * @param {Window} win
* @param {Object} settings - Settings rendered into sidebar HTML
* @return {string} * @return {string}
*/ */
function clientType(win, settings = {}) { function clientType(win, settings = {}) {
...@@ -65,9 +64,18 @@ function clientType(win, settings = {}) { ...@@ -65,9 +64,18 @@ function clientType(win, settings = {}) {
return type; return type;
} }
/**
* @typedef Analytics
* @prop {() => any} sendPageView
* @prop {(action: string, label?: string, value?: number) => void} track
* @prop {Object.<string, string>} events
*/
/** /**
* Analytics service for tracking page views and user interactions with the * Analytics service for tracking page views and user interactions with the
* application. * application.
* @param {Window} $window - Test seam
* @return {Analytics}
*/ */
// @ngInject // @ngInject
export default function analytics($window, settings) { export default function analytics($window, settings) {
...@@ -78,6 +86,7 @@ export default function analytics($window, settings) { ...@@ -78,6 +86,7 @@ export default function analytics($window, settings) {
// is replaced when analytics.js fully loads. // is replaced when analytics.js fully loads.
// //
// See https://developers.google.com/analytics/devguides/collection/analyticsjs/command-queue-reference // See https://developers.google.com/analytics/devguides/collection/analyticsjs/command-queue-reference
// @ts-ignore The window interface needs to be expanded to include this property
const commandQueue = () => $window.ga || noop; const commandQueue = () => $window.ga || noop;
return { return {
......
/** @typedef {import('../../types/api').Annotation} Annotation */
/** @typedef {import('../../types/annotator').AnnotationData} AnnotationData */
/** /**
* A service for creating, manipulating and persisting annotations and their * A service for creating, manipulating and persisting annotations and their
* application-store representations. Interacts with API services as needed. * application-store representations. Interacts with API services as needed.
...@@ -17,6 +20,8 @@ export default function annotationsService(api, store) { ...@@ -17,6 +20,8 @@ export default function annotationsService(api, store) {
/** /**
* Apply changes for the given `annotation` from its draft in the store (if * Apply changes for the given `annotation` from its draft in the store (if
* any) and return a new object with those changes integrated. * any) and return a new object with those changes integrated.
*
* @param {Annotation} annotation
*/ */
function applyDraftChanges(annotation) { function applyDraftChanges(annotation) {
const changes = {}; const changes = {};
...@@ -36,6 +41,10 @@ export default function annotationsService(api, store) { ...@@ -36,6 +41,10 @@ export default function annotationsService(api, store) {
/** /**
* Extend new annotation objects with defaults and permissions. * Extend new annotation objects with defaults and permissions.
*
* @param {AnnotationData} annotationData
* @param {Date} now
* @return {Annotation}
*/ */
function initialize(annotationData, now = new Date()) { function initialize(annotationData, now = new Date()) {
const defaultPrivacy = store.getDefault('annotationPrivacy'); const defaultPrivacy = store.getDefault('annotationPrivacy');
...@@ -50,27 +59,29 @@ export default function annotationsService(api, store) { ...@@ -50,27 +59,29 @@ export default function annotationsService(api, store) {
// as it has not been persisted to the service. // as it has not been persisted to the service.
const $tag = generateHexString(8); const $tag = generateHexString(8);
let permissions = defaultPermissions(userid, groupid, defaultPrivacy); /** @type {Annotation} */
const annotation = Object.assign(
// Highlights are peculiar in that they always have private permissions
if (metadata.isHighlight(annotationData)) {
permissions = privatePermissions(userid);
}
return Object.assign(
{ {
created: now.toISOString(), created: now.toISOString(),
group: groupid, group: groupid,
permissions, permissions: defaultPermissions(userid, groupid, defaultPrivacy),
tags: [], tags: [],
text: '', text: '',
updated: now.toISOString(), updated: now.toISOString(),
user: userid, user: userid,
user_info: userInfo, user_info: userInfo,
$tag: $tag, $tag: $tag,
hidden: false,
links: {},
}, },
annotationData annotationData
); );
// Highlights are peculiar in that they always have private permissions
if (metadata.isHighlight(annotation)) {
annotation.permissions = privatePermissions(userid);
}
return annotation;
} }
/** /**
......
...@@ -19,6 +19,8 @@ function translateResponseToError(response, data) { ...@@ -19,6 +19,8 @@ function translateResponseToError(response, data) {
/** /**
* Return a shallow clone of `obj` with all client-only properties removed. * Return a shallow clone of `obj` with all client-only properties removed.
* Client-only properties are marked by a '$' prefix. * Client-only properties are marked by a '$' prefix.
*
* @param {Object} obj
*/ */
function stripInternalProperties(obj) { function stripInternalProperties(obj) {
const result = {}; const result = {};
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
* @throws {Error} * @throws {Error}
* This function may throw an exception if the browser rejects the attempt * This function may throw an exception if the browser rejects the attempt
* to copy text. * to copy text.
* @param {string} text
*/ */
export function copyText(text) { export function copyText(text) {
const temp = document.createElement('pre'); const temp = document.createElement('pre');
......
...@@ -31,7 +31,7 @@ export function getElementHeightWithMargins(element) { ...@@ -31,7 +31,7 @@ export function getElementHeightWithMargins(element) {
* @param {string[]} events * @param {string[]} events
* @param {EventListener} listener * @param {EventListener} listener
* @param {Object} options * @param {Object} options
* @param {boolean} [options.useCapture] * @param {boolean} [options.useCapture]
* @return {function} Function which removes the event listeners. * @return {function} Function which removes the event listeners.
*/ */
export function listen(element, events, listener, { useCapture = false } = {}) { export function listen(element, events, listener, { useCapture = false } = {}) {
......
...@@ -9,7 +9,3 @@ ...@@ -9,7 +9,3 @@
export function orgName(group) { export function orgName(group) {
return group.organization && group.organization.name; return group.organization && group.organization.name;
} }
export function trackViewGroupActivity(analytics) {
analytics.track(analytics.events.GROUP_VIEW_ACTIVITY);
}
import { events } from '../../services/analytics';
import * as groupListItemCommon from '../group-list-item-common'; import * as groupListItemCommon from '../group-list-item-common';
describe('sidebar/util/groupListItemCommon', () => { describe('sidebar/util/groupListItemCommon', () => {
describe('trackViewGroupActivity', () => {
it('triggers the GROUP_VIEW_ACTIVITY event when called', () => {
const fakeAnalytics = {
track: sinon.stub(),
events,
};
groupListItemCommon.trackViewGroupActivity(fakeAnalytics);
assert.calledWith(
fakeAnalytics.track,
fakeAnalytics.events.GROUP_VIEW_ACTIVITY
);
});
});
describe('orgName', () => { describe('orgName', () => {
it('returns the organization name if it exists', () => { it('returns the organization name if it exists', () => {
const fakeGroup = { id: 'groupid', organization: { name: 'org' } }; const fakeGroup = { id: 'groupid', organization: { name: 'org' } };
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
"sidebar/store/create-store.js", "sidebar/store/create-store.js",
"sidebar/store/debug-middleware.js", "sidebar/store/debug-middleware.js",
"sidebar/store/use-store.js", "sidebar/store/use-store.js",
"sidebar/store/utils.js",
], ],
"exclude": [ "exclude": [
......
/**
* Type definitions for objects passed between the annotator and sidebar.
*/
/** @typedef {import("./api").Target} Target */
/**
* @typedef AnnotationData
* @prop {string} uri
* @prop {Target[]} target
* @prop {string} $tag
* @prop {boolean} [$highlight]
* @prop {DocumentMetadata} document
*/
/**
* @typedef DocumentMetadata
* @prop {string} title
* @prop {Object[]} link
* @prop {string} link.rel
* @prop {string} link.type
* // html pages
* @prop {Object.<string, string[]>} [dc]
* @prop {Object.<string, string[]>} [eprints]
* @prop {Object.<string, string[]>} [facebook]
* @prop {Object.<string, string[]>} [highwire]
* @prop {Object.<string, string[]>} [prism]
* @prop {Object.<string, string[]>} [twitter]
* // pdf files
* @prop {string} [documentFingerprint]
*/
// Make TypeScript treat this file as a module.
export const unused = {};
...@@ -33,6 +33,12 @@ ...@@ -33,6 +33,12 @@
* @typedef {TextQuoteSelector | TextPositionSelector | RangeSelector} Selector * @typedef {TextQuoteSelector | TextPositionSelector | RangeSelector} Selector
*/ */
/**
* @typedef Target
* @prop {string} source
* @prop {Selector[]} [selector]
*
/** /**
* TODO - Fill out remaining properties * TODO - Fill out remaining properties
* *
...@@ -58,9 +64,7 @@ ...@@ -58,9 +64,7 @@
* @prop {string[]} permissions.update * @prop {string[]} permissions.update
* @prop {string[]} permissions.delete * @prop {string[]} permissions.delete
* *
* @prop {Object[]} target * @prop {Target[]} target
* @prop {string} target.source
* @prop {Selector[]} [target.selector]
* *
* @prop {Object} [moderation] * @prop {Object} [moderation]
* @prop {number} moderation.flagCount * @prop {number} moderation.flagCount
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment