Repository: temporalio/ui
Pull request #246 (2022-01-25 21:42:16)

Improve test coverage and remove unused utilities
- Remove utilities functions that are no longer being used
- Improve test coverage on `to-duration.ts` and `to-url.ts`.
- Improve grammar to `toString` in `to-duration.ts`.

cloudflare-pages[bot]: ## Deploying with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>fe2a03c</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://b8f36fbd.ui-c3x.pages.dev'>https://b8f36fbd.ui-c3x.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/?to=/:account/pages/view/ui/b8f36fbd-d7ae-49d4-a7ba-9a0d40b1bca6)


Patch:
diff --git a/src/lib/models/events-group.ts b/src/lib/models/events-group.ts
index 90cb0afd..ac706471 100644
--- a/src/lib/models/events-group.ts
+++ b/src/lib/models/events-group.ts
@@ -2,6 +2,17 @@ type ScheduledActivityEvent = HistoryEventWithId & {
   eventType: 'ActivityTaskScheduled';
 };
 
+type CompactEventType =
+  | ActivityType
+  | TimerType
+  | SignalType
+  | MarkerType
+  | ChildType;
+
+type CompactEvent = HistoryEventWithId & {
+  eventType: CompactEventType;
+};
+
 const activityTypes = [
   'ActivityTaskCanceled',
   'ActivityTaskCancelRequested',
@@ -13,9 +24,7 @@ const activityTypes = [
 ];
 
 const timerTypes = ['TimerStarted', 'TimerCanceled', 'TimerFired'];
-
 const signalTypes = ['WorkflowExecutionSignaled'];
-
 const markerTypes = ['MarkerRecorded'];
 
 const childTypes = [
@@ -24,17 +33,6 @@ const childTypes = [
   'ChildWorkflowExecutionCompleted',
 ];
 
-type CompactEventType =
-  | ActivityType
-  | TimerType
-  | SignalType
-  | MarkerType
-  | ChildType;
-
-type CompactEvent = HistoryEventWithId & {
-  eventType: CompactEventType;
-};
-
 const isCompactEvent = (event: HistoryEventWithId): event is CompactEvent => {
   if (
     activityTypes.includes(event.eventType) ||
diff --git a/src/lib/utilities/get-attributes-from-event.ts b/src/lib/utilities/get-attributes-from-event.ts
deleted file mode 100644
index 224094ab..00000000
--- a/src/lib/utilities/get-attributes-from-event.ts
+++ /dev/null
@@ -1,225 +0,0 @@
-import * as eventTypes from './is-event-type';
-import { pick } from './pick';
-
-interface EventSummary {
-  type: string;
-  attributes: Partial<EventAttribute>;
-}
-
-export const getAttributesFromEvent = (event: unknown): EventSummary => {
-  if (eventTypes.isActivityTaskCancelRequestedEvent(event)) {
-    return {
-      type: 'activityTaskCancelRequestedEventAttributes',
-      attributes: pick(
-        event.activityTaskCancelRequestedEventAttributes,
-        'workflowTaskCompletedEventId',
-      ),
-    };
-  }
-  if (eventTypes.isActivityTaskCompletedEvent(event)) {
-    return {
-      type: 'activityTaskCompletedEventAttributes',
-      attributes: pick(event.activityTaskCompletedEventAttributes, 'result'),
-    };
-  }
-  if (eventTypes.isActivityTaskFailedEvent(event)) {
-    return {
-      type: 'activityTaskFailedEventAttributes',
-      attributes: pick(
-        event.activityTaskFailedEventAttributes,
-        'failure',
-        'identity',
-        'startedEventId',
-      ),
-    };
-  }
-  if (eventTypes.isActivityTaskScheduledEvent(event)) {
-    return {
-      type: 'activityTaskScheduledEventAttributes',
-      attributes: pick(
-        event.activityTaskScheduledEventAttributes,
-        'input',
-        'activityId',
-        'scheduleToCloseTimeout',
-      ),
-    };
-  }
-  if (eventTypes.isActivityTaskStartedEvent(event)) {
-    return {
-      type: 'activityTaskStartedEventAttributes',
-      attributes: pick(
-        event.activityTaskStartedEventAttributes,
-        'lastFailure',
-        'attempt',
-      ),
-    };
-  }
-  if (eventTypes.isActivityTaskTimedOutEvent(event)) {
-    return {
-      type: 'activityTaskTimedOutEventAttributes',
-      attributes: pick(
-        event.activityTaskTimedOutEventAttributes,
-        'failure',
-        'startedEventId',
-      ),
-    };
-  }
-  if (eventTypes.isChildWorkflowExecutionCompletedEvent(event)) {
-    return {
-      type: 'childWorkflowExecutionCompletedEventAttributes',
-      attributes: pick(
-        event.childWorkflowExecutionCompletedEventAttributes,
-        'result',
-        'initiatedEventId',
-      ),
-    };
-  }
-  if (eventTypes.isChildWorkflowExecutionStartedEvent(event)) {
-    return {
-      type: 'childWorkflowExecutionStartedEventAttributes',
-      attributes: pick(
-        event.childWorkflowExecutionStartedEventAttributes,
-        'initiatedEventId',
-      ),
-    };
-  }
-  if (eventTypes.isWorkflowTaskCompletedEvent(event)) {
-    return {
-      type: 'workflowTaskCompletedEventAttributes',
-      attributes: pick(
-        event.workflowTaskCompletedEventAttributes,
-        'identity',
-        'startedEventId',
-      ),
-    };
-  }
-  if (eventTypes.isWorkflowTaskScheduledEvent(event)) {
-    return {
-      type: 'workflowTaskScheduledEventAttributes',
-      attributes: pick(
-        event.workflowTaskScheduledEventAttributes,
-        'startToCloseTimeout',
-      ),
-    };
-  }
-  if (eventTypes.isWorkflowTaskStartedEvent(event)) {
-    return {
-      type: 'workflowTaskStartedEventAttributes',
-      attributes: pick(
-        event.workflowTaskStartedEventAttributes,
-        'identity',
-        'requestId',
-      ),
-    };
-  }
-  if (eventTypes.isWorkflowTaskTimedOutEvent(event)) {
-    return {
-      type: 'workflowTaskTimedOutEventAttributes',
-      attributes: pick(
-        event.workflowTaskTimedOutEventAttributes,
-        'timeoutType',
-        'scheduledEventId',
-      ),
-    };
-  }
-  if (eventTypes.isExternalWorkflowExecutionSignaledEvent(event)) {
-    return {
-      type: 'externalWorkflowExecutionSignaledEventAttributes',
-      attributes: pick(
-        event.externalWorkflowExecutionSignaledEventAttributes,
-        'workflowExecution',
-        'initiatedEventId',
-      ),
-    };
-  }
-  if (eventTypes.isStartChildWorkflowExecutionInitiatedEvent(event)) {
-    return {
-      type: 'startChildWorkflowExecutionInitiatedEventAttributes',
-      attributes: pick(
-        event.startChildWorkflowExecutionInitiatedEventAttributes,
-        'input',
-        'workflowId',
-        'workflowTaskTimeout',
-      ),
-    };
-  }
-  if (eventTypes.isSignalExternalWorkflowExecutionInitiatedEvent(event)) {
-    return {
-      type: 'signalExternalWorkflowExecutionInitiatedEventAttributes',
-      attributes: pick(
-        event.signalExternalWorkflowExecutionInitiatedEventAttributes,
-        'input',
-        'workflowTaskCompletedEventId',
-      ),
-    };
-  }
-  if (eventTypes.isTimerStartedEvent(event)) {
-    return {
-      type: 'timerStartedEventAttributes',
-      attributes: pick(event.timerStartedEventAttributes, 'startToFireTimeout'),
-    };
-  }
-  if (eventTypes.isWorkflowExecutionStartedEvent(event)) {
-    return {
-      type: 'workflowExecutionStartedEventAttributes',
-      attributes: pick(
-        event.workflowExecutionStartedEventAttributes,
-        'input',
-        'attempt',
-        'lastCompletionResult',
-      ),
-    };
-  }
-  if (eventTypes.isWorkflowExecutionCompletedEvent(event)) {
-    return {
-      type: 'workflowExecutionCompletedEventAttributes',
-      attributes: pick(
-        event.workflowExecutionCompletedEventAttributes,
-        'result',
-        'workflowTaskCompletedEventId',
-      ),
-    };
-  }
-  if (eventTypes.isWorkflowExecutionFailedEvent(event)) {
-    return {
-      type: 'workflowExecutionFailedEventAttributes',
-      attributes: pick(
-        event.workflowExecutionFailedEventAttributes,
-        'failure',
-        'newExecutionRunId',
-        'retryState',
-      ),
-    };
-  }
-  if (eventTypes.isWorkflowExecutionTimedOutEvent(event)) {
-    return {
-      type: 'workflowExecutionTimedOutEventAttributes',
-      attributes: pick(
-        event.workflowExecutionTimedOutEventAttributes,
-        'retryState',
-      ),
-    };
-  }
-  if (eventTypes.isWorkflowTaskFailedEvent(event)) {
-    return {
-      type: 'workflowTaskFailedEventAttributes',
-      attributes: pick(
-        event.workflowTaskFailedEventAttributes,
-        'failure',
-        'newRunId',
-        'startedEventId',
-      ),
-    };
-  }
-  if (eventTypes.isChildWorkflowExecutionFailedEvent(event)) {
-    return {
-      type: 'childWorkflowExecutionFailedEventAttributes',
-      attributes: pick(
-        event.childWorkflowExecutionFailedEventAttributes,
-        'failure',
-        'retryState',
-        'startedEventId',
-      ),
-    };
-  }
-};
diff --git a/src/lib/utilities/get-task-queue-url.ts b/src/lib/utilities/get-task-queue-url.ts
deleted file mode 100644
index 3b1ec2dd..00000000
--- a/src/lib/utilities/get-task-queue-url.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export const getTaskQueueUrl = (
-  namespace: string,
-  taskQueue: string,
-): string => {
-  if (!taskQueue) return;
-  return `/namespaces/${namespace}/queues/${taskQueue}`;
-};
diff --git a/src/lib/utilities/is-event-type.ts b/src/lib/utilities/is-event-type.ts
deleted file mode 100644
index e9f825d2..00000000
--- a/src/lib/utilities/is-event-type.ts
+++ /dev/null
@@ -1,239 +0,0 @@
-export const isWorkflowExecutionStartedEvent = (
-  event: HistoryEvent,
-): event is WorkflowExecutionStartedEvent => {
-  return !!event.workflowExecutionStartedEventAttributes;
-};
-
-export const isWorkflowExecutionCompletedEvent = (
-  event: HistoryEvent,
-): event is WorkflowExecutionCompletedEvent => {
-  return !!event.workflowExecutionCompletedEventAttributes;
-};
-
-export const isWorkflowExecutionFailedEvent = (
-  event: HistoryEvent,
-): event is WorkflowExecutionFailedEvent => {
-  return !!event.workflowExecutionFailedEventAttributes;
-};
-
-export const isWorkflowExecutionTimedOutEvent = (
-  event: HistoryEvent,
-): event is WorkflowExecutionTimedOutEvent => {
-  return !!event.workflowExecutionTimedOutEventAttributes;
-};
-
-export const isWorkflowTaskScheduledEvent = (
-  event: HistoryEvent,
-): event is WorkflowTaskScheduledEvent => {
-  return !!event.workflowTaskScheduledEventAttributes;
-};
-
-export const isWorkflowTaskStartedEvent = (
-  event: HistoryEvent,
-): event is WorkflowTaskStartedEvent => {
-  return !!event.workflowTaskStartedEventAttributes;
-};
-
-export const isWorkflowTaskCompletedEvent = (
-  event: HistoryEvent,
-): event is WorkflowTaskCompletedEvent => {
-  return !!event.workflowTaskCompletedEventAttributes;
-};
-
-export const isWorkflowTaskTimedOutEvent = (
-  event: HistoryEvent,
-): event is WorkflowTaskTimedOutEvent => {
-  return !!event.workflowTaskTimedOutEventAttributes;
-};
-
-export const isWorkflowTaskFailedEvent = (
-  event: HistoryEvent,
-): event is WorkflowTaskFailedEvent => {
-  return !!event.workflowTaskFailedEventAttributes;
-};
-
-export const isActivityTaskScheduledEvent = (
-  event: HistoryEvent,
-): event is ActivityTaskScheduledEvent => {
-  return !!event.activityTaskScheduledEventAttributes;
-};
-
-export const isActivityTaskStartedEvent = (
-  event: HistoryEvent,
-): event is ActivityTaskStartedEvent => {
-  return !!event.activityTaskStartedEventAttributes;
-};
-
-export const isActivityTaskCompletedEvent = (
-  event: HistoryEvent,
-): event is ActivityTaskCompletedEvent => {
-  return !!event.activityTaskCompletedEventAttributes;
-};
-
-export const isActivityTaskFailedEvent = (
-  event: HistoryEvent,
-): event is ActivityTaskFailedEvent => {
-  return !!event.activityTaskFailedEventAttributes;
-};
-
-export const isActivityTaskTimedOutEvent = (
-  event: HistoryEvent,
-): event is ActivityTaskTimedOutEvent => {
-  return !!event.activityTaskTimedOutEventAttributes;
-};
-
-export const isTimerStartedEvent = (
-  event: HistoryEvent,
-): event is TimerStartedEvent => {
-  return !!event.timerStartedEventAttributes;
-};
-
-export const isTimerFiredEvent = (
-  event: HistoryEvent,
-): event is TimerFiredEvent => {
-  return !!event.timerFiredEventAttributes;
-};
-
-export const isActivityTaskCancelRequestedEvent = (
-  event: HistoryEvent,
-): event is ActivityTaskCancelRequestedEvent => {
-  return !!event.activityTaskCancelRequestedEventAttributes;
-};
-
-export const isActivityTaskCanceledEvent = (
-  event: HistoryEvent,
-): event is ActivityTaskCanceledEvent => {
-  return !!event.activityTaskCanceledEventAttributes;
-};
-
-export const isTimerCanceledEvent = (
-  event: HistoryEvent,
-): event is TimerCanceledEvent => {
-  return !!event.timerCanceledEventAttributes;
-};
-
-export const isMarkerRecordedEvent = (
-  event: HistoryEvent,
-): event is MarkerRecordedEvent => {
-  return !!event.markerRecordedEventAttributes;
-};
-
-export const isWorkflowExecutionSignaledEvent = (
-  event: HistoryEvent,
-): event is WorkflowExecutionSignaledEvent => {
-  return !!event.workflowExecutionSignaledEventAttributes;
-};
-
-export const isWorkflowExecutionTerminatedEvent = (
-  event: HistoryEvent,
-): event is WorkflowExecutionTerminatedEvent => {
-  return !!event.workflowExecutionTerminatedEventAttributes;
-};
-
-export const isWorkflowExecutionCancelRequestedEvent = (
-  event: HistoryEvent,
-): event is WorkflowExecutionCancelRequestedEvent => {
-  return !!event.workflowExecutionCancelRequestedEventAttributes;
-};
-
-export const isWorkflowExecutionCanceledEvent = (
-  event: HistoryEvent,
-): event is WorkflowExecutionCanceledEvent => {
-  return !!event.workflowExecutionCanceledEventAttributes;
-};
-
-export const isRequestCancelExternalWorkflowExecutionInitiatedEvent = (
-  event: HistoryEvent,
-): event is RequestCancelExternalWorkflowExecutionInitiatedEvent => {
-  return !!event.requestCancelExternalWorkflowExecutionInitiatedEventAttributes;
-};
-
-export const isRequestCancelExternalWorkflowExecutionFailedEvent = (
-  event: HistoryEvent,
-): event is RequestCancelExternalWorkflowExecutionFailedEvent => {
-  return !!event.requestCancelExternalWorkflowExecutionFailedEventAttributes;
-};
-
-export const isExternalWorkflowExecutionCancelRequestedEvent = (
-  event: HistoryEvent,
-): event is ExternalWorkflowExecutionCancelRequestedEvent => {
-  return !!event.externalWorkflowExecutionCancelRequestedEventAttributes;
-};
-
-export const isWorkflowExecutionContinuedAsNewEvent = (
-  event: HistoryEvent,
-): event is WorkflowExecutionContinuedAsNewEvent => {
-  return !!event.workflowExecutionContinuedAsNewEventAttributes;
-};
-
-export const isStartChildWorkflowExecutionInitiatedEvent = (
-  event: HistoryEvent,
-): event is StartChildWorkflowExecutionInitiatedEvent => {
-  return !!event.startChildWorkflowExecutionInitiatedEventAttributes;
-};
-
-export const isStartChildWorkflowExecutionFailedEvent = (
-  event: HistoryEvent,
-): event is StartChildWorkflowExecutionFailedEvent => {
-  return !!event.startChildWorkflowExecutionFailedEventAttributes;
-};
-
-export const isChildWorkflowExecutionStartedEvent = (
-  event: HistoryEvent,
-): event is ChildWorkflowExecutionStartedEvent => {
-  return !!event.childWorkflowExecutionStartedEventAttributes;
-};
-
-export const isChildWorkflowExecutionCompletedEvent = (
-  event: HistoryEvent,
-): event is ChildWorkflowExecutionCompletedEvent => {
-  return !!event.childWorkflowExecutionCompletedEventAttributes;
-};
-
-export const isChildWorkflowExecutionFailedEvent = (
-  event: HistoryEvent,
-): event is ChildWorkflowExecutionFailedEvent => {
-  return !!event.childWorkflowExecutionFailedEventAttributes;
-};
-
-export const isChildWorkflowExecutionCanceledEvent = (
-  event: HistoryEvent,
-): event is ChildWorkflowExecutionCanceledEvent => {
-  return !!event.childWorkflowExecutionCanceledEventAttributes;
-};
-
-export const isChildWorkflowExecutionTimedOutEvent = (
-  event: HistoryEvent,
-): event is ChildWorkflowExecutionTimedOutEvent => {
-  return !!event.childWorkflowExecutionTimedOutEventAttributes;
-};
-
-export const isChildWorkflowExecutionTerminatedEvent = (
-  event: HistoryEvent,
-): event is ChildWorkflowExecutionTerminatedEvent => {
-  return !!event.childWorkflowExecutionTerminatedEventAttributes;
-};
-
-export const isSignalExternalWorkflowExecutionInitiatedEvent = (
-  event: HistoryEvent,
-): event is SignalExternalWorkflowExecutionInitiatedEvent => {
-  return !!event.signalExternalWorkflowExecutionInitiatedEventAttributes;
-};
-
-export const isSignalExternalWorkflowExecutionFailedEvent = (
-  event: HistoryEvent,
-): event is SignalExternalWorkflowExecutionFailedEvent => {
-  return !!event.signalExternalWorkflowExecutionFailedEventAttributes;
-};
-
-export const isExternalWorkflowExecutionSignaledEvent = (
-  event: HistoryEvent,
-): event is ExternalWorkflowExecutionSignaledEvent => {
-  return !!event.externalWorkflowExecutionSignaledEventAttributes;
-};
-
-export const isUpsertWorkflowSearchAttributesEvent = (
-  event: HistoryEvent,
-): event is UpsertWorkflowSearchAttributesEvent => {
-  return !!event.upsertWorkflowSearchAttributesEventAttributes;
-};
diff --git a/src/lib/utilities/to-duration.ts b/src/lib/utilities/to-duration.ts
index bc26fe73..00618d9a 100644
--- a/src/lib/utilities/to-duration.ts
+++ b/src/lib/utilities/to-duration.ts
@@ -32,7 +32,7 @@ export const durations = [
 
 const durationPattern = new RegExp(`(\\d+)\\s(${durationKeys.join('|')})`);
 
-const isDurationKey = (key: unknown): key is DurationKey => {
+export const isDurationKey = (key: unknown): key is DurationKey => {
   if (typeof key !== 'string') return false;
 
   for (const durationKey of durationKeys) {
@@ -71,8 +71,11 @@ export const toDuration = (value: string): Duration => {
 };
 
 export const toString = (duration: Duration): string => {
-  const units = Object.keys(duration)[0];
+  let units = Object.keys(duration)[0];
   const amount: number = duration[units];
+
+  if (amount === 1) units = units.slice(0, units.length - 1);
+
   return `${amount} ${units}`;
 };
 


Test patch:
diff --git a/src/lib/utilities/get-attributes-from-event.test.ts b/src/lib/utilities/get-attributes-from-event.test.ts
deleted file mode 100644
index 6d89a7f4..00000000
--- a/src/lib/utilities/get-attributes-from-event.test.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { getAttributesFromEvent } from './get-attributes-from-event';
-
-describe(getAttributesFromEvent, () => {
-  it('should return back unchanged attributes of result and workflowTaskCompletedEventId', () => {
-    const event = {
-      eventId: '11',
-      eventTime: '2021-12-02T21:51:06.992475543Z',
-      eventType: 'WorkflowExecutionCompleted',
-      version: '0',
-      taskId: '1048850',
-      workflowExecutionCompletedEventAttributes: {
-        result: null,
-        workflowTaskCompletedEventId: '10',
-      },
-      id: '11',
-    };
-
-    expect(getAttributesFromEvent(event)).toStrictEqual({
-      attributes: { result: null, workflowTaskCompletedEventId: '10' },
-      type: 'workflowExecutionCompletedEventAttributes',
-    });
-  });
-
-  it('should return back unchanged attributes only result', () => {
-    const event = {
-      eventId: '7',
-      eventTime: '2021-12-02T21:51:06.976972251Z',
-      eventType: 'ActivityTaskCompleted',
-      version: '0',
-      taskId: '1048841',
-      activityTaskCompletedEventAttributes: {
-        result: null,
-        scheduledEventId: '5',
-        startedEventId: '6',
-        identity: '74553@Josephs-MacBook-Pro-2.local@',
-      },
-      id: '7',
-    };
-    expect(getAttributesFromEvent(event)).toStrictEqual({
-      attributes: { result: null },
-      type: 'activityTaskCompletedEventAttributes',
-    });
-  });
-});
diff --git a/src/lib/utilities/to-duration.test.ts b/src/lib/utilities/to-duration.test.ts
index ea9b8fd7..c7879bd3 100644
--- a/src/lib/utilities/to-duration.test.ts
+++ b/src/lib/utilities/to-duration.test.ts
@@ -1,4 +1,12 @@
-import { toDuration, toDate, fromDate, isDurationString } from './to-duration';
+import {
+  toDuration,
+  toDate,
+  fromDate,
+  isDurationString,
+  isDurationKey,
+  toString,
+  isDuration,
+} from './to-duration';
 
 describe(toDuration, () => {
   it('should correctly parse "Last 10 minutes"', () => {
@@ -93,6 +101,14 @@ describe(fromDate, () => {
     jest.useRealTimers();
   });
 
+  it('should produce a duration based on a 2 years in the past', () => {
+    jest.useFakeTimers().setSystemTime(new Date('2020-01-01').getTime());
+    const twoYearsEarlier = '2018-01-01T00:00:00Z';
+
+    const result = fromDate(twoYearsEarlier);
+    expect(result).toEqual({ years: 2 });
+  });
+
   it('should produce a duration based on a 90 days in the past', () => {
     jest.useFakeTimers().setSystemTime(new Date('2020-01-01').getTime());
     const ninetyDaysEarlier = '2019-10-03T00:00:00Z';
@@ -108,6 +124,94 @@ describe(fromDate, () => {
     const result = fromDate(sixtyDaysEarlier);
     expect(result).toEqual({ days: 60 });
   });
+
+  it('should produce a duration based on 23 hours in the past', () => {
+    jest.useFakeTimers().setSystemTime(new Date('2020-01-01').getTime());
+    const twentyThreeHoursEarlier = '2019-12-31T01:00:00Z';
+
+    const result = fromDate(twentyThreeHoursEarlier);
+    expect(result).toEqual({ hours: 23 });
+  });
+
+  it('should produce a duration based on 30 minutes in the past', () => {
+    jest.useFakeTimers().setSystemTime(new Date('2020-01-01').getTime());
+    const thirtyMinutesEarlier = '2019-12-31T23:30:00Z';
+
+    const result = fromDate(thirtyMinutesEarlier);
+    expect(result).toEqual({ minutes: 30 });
+  });
+
+  it('should produce a duration based on 10 seconds in the past', () => {
+    jest.useFakeTimers().setSystemTime(new Date('2020-01-01').getTime());
+    const tenSecondsEarlier = '2019-12-31T23:59:50Z';
+
+    const result = fromDate(tenSecondsEarlier);
+    expect(result).toEqual({ seconds: 10 });
+  });
+});
+
+describe(isDuration, () => {
+  it('should return false if given null', () => {
+    expect(isDuration(null)).toBe(false);
+  });
+
+  it('should return false if given an object with an invalid key', () => {
+    expect(isDuration({ invalid: 4 })).toBe(false);
+  });
+
+  it('should return true if given an object with an valid key', () => {
+    expect(isDuration({ years: 4 })).toBe(true);
+  });
+});
+
+describe(isDurationKey, () => {
+  it('should return false if given a number', () => {
+    expect(isDurationKey(2)).toBe(false);
+  });
+
+  it('should return false if given a boolean', () => {
+    expect(isDurationKey(true)).toBe(false);
+  });
+
+  it('should return false if given an object', () => {
+    expect(isDurationKey({})).toBe(false);
+  });
+
+  it('should return false if given an array', () => {
+    expect(isDurationKey([])).toBe(false);
+  });
+
+  it('should return true if given "years" as a duration key', () => {
+    expect(isDurationKey('years')).toBe(true);
+  });
+
+  it('should return true if given "months" as a duration key', () => {
+    expect(isDurationKey('months')).toBe(true);
+  });
+
+  it('should return true if given "weeks" as a duration key', () => {
+    expect(isDurationKey('weeks')).toBe(true);
+  });
+
+  it('should return true if given "days" as a duration key', () => {
+    expect(isDurationKey('days')).toBe(true);
+  });
+
+  it('should return true if given "hours" as a duration key', () => {
+    expect(isDurationKey('hours')).toBe(true);
+  });
+
+  it('should return true if given "minutes" as a duration key', () => {
+    expect(isDurationKey('minutes')).toBe(true);
+  });
+
+  it('should return true if given "seconds" as a duration key', () => {
+    expect(isDurationKey('seconds')).toBe(true);
+  });
+
+  it('should return false if given an invalid string', () => {
+    expect(isDurationKey('not valid')).toBe(false);
+  });
 });
 
 describe(isDurationString, () => {
@@ -127,3 +231,61 @@ describe(isDurationString, () => {
     expect(isDurationString('undefined')).toBe(false);
   });
 });
+
+describe(toString, () => {
+  it('should correctly format 3 years', () => {
+    expect(toString({ years: 3 })).toBe('3 years');
+  });
+
+  it('should correctly format 3 months', () => {
+    expect(toString({ months: 3 })).toBe('3 months');
+  });
+
+  it('should correctly format 3 weeks', () => {
+    expect(toString({ weeks: 3 })).toBe('3 weeks');
+  });
+
+  it('should correctly format 3 days', () => {
+    expect(toString({ days: 3 })).toBe('3 days');
+  });
+
+  it('should correctly format 3 hours', () => {
+    expect(toString({ hours: 3 })).toBe('3 hours');
+  });
+
+  it('should correctly format 3 minutes', () => {
+    expect(toString({ minutes: 3 })).toBe('3 minutes');
+  });
+
+  it('should correctly format 3 seconds', () => {
+    expect(toString({ seconds: 3 })).toBe('3 seconds');
+  });
+
+  it('should correctly format 1 year', () => {
+    expect(toString({ years: 1 })).toBe('1 year');
+  });
+
+  it('should correctly format 1 month', () => {
+    expect(toString({ months: 1 })).toBe('1 month');
+  });
+
+  it('should correctly format 1 week', () => {
+    expect(toString({ weeks: 1 })).toBe('1 week');
+  });
+
+  it('should correctly format 1 day', () => {
+    expect(toString({ days: 1 })).toBe('1 day');
+  });
+
+  it('should correctly format 1 hour', () => {
+    expect(toString({ hours: 1 })).toBe('1 hour');
+  });
+
+  it('should correctly format 1 minute', () => {
+    expect(toString({ minutes: 1 })).toBe('1 minute');
+  });
+
+  it('should correctly format 1 second', () => {
+    expect(toString({ seconds: 1 })).toBe('1 second');
+  });
+});
diff --git a/src/lib/utilities/to-url.test.ts b/src/lib/utilities/to-url.test.ts
index dc072671..6a57f6d8 100644
--- a/src/lib/utilities/to-url.test.ts
+++ b/src/lib/utilities/to-url.test.ts
@@ -9,4 +9,9 @@ describe(toURL, () => {
     const params = new URLSearchParams({ a: 'hello' });
     expect(toURL('/workflows', params)).toBe('/workflows?a=hello');
   });
+
+  it('should turn an object into a query string', () => {
+    const params = { a: 'hello' };
+    expect(toURL('/workflows', params)).toBe('/workflows?a=hello');
+  });
 });