I have function called setMockDate that looks like:
/**
* #param {Date} expected
* #returns {Function} Call to remove Date mocking
*/
const setMockDate = (expected: Date): AnyObject => {
const RealDate = Date
function MockDate(mockOverride?: Date | number) {
return new RealDate(mockOverride || expected)
}
MockDate.UTC = RealDate.UTC
MockDate.parse = RealDate.parse
MockDate.now = () => expected.getTime()
MockDate.prototype = RealDate.prototype
// eslint-disable-next-line #typescript-eslint/no-explicit-any
global.Date = MockDate as any
return () => {
global.Date = RealDate
}
}
export default setMockDate
and used like:
test('Should change date', () => {
const mockDate = new Date('Feb 22, 2021 11:59:00')
setMockDate(mockDate)
expect(new Date()).toEqual(mockDate)
})
I wanted to change the MockDate function in setMockDate() to use an arrow function like:
const MockDate = (mockOverride?: Date | number) => {
return new RealDate(mockOverride || expected)
}
However I get TypeError:
TypeError: Date is not a constructor
Why I am getting this error when changing to use an arrow function?
MockDate is supposed to mimic new Date() which is a constructor and arrow function cannot be used as a constructor
More information here:
https://dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
I updated my helper function to:
import { AnyObject } from 'typings/custom'
/**
* #param {Date} expected
* #returns {Function} Call to remove Date mocking
*/
const setMockDate = (expected: Date): AnyObject => {
const RealDate = Date
function MockDate(mockOverride?: Date | number) {
return new RealDate(mockOverride || expected)
}
MockDate.now = () => expected.getTime()
MockDate.prototype = RealDate.prototype
// eslint-disable-next-line #typescript-eslint/no-explicit-any
global.Date = MockDate as any
return () => {
global.Date = RealDate
}
}
export default setMockDate
Using this helper looks like:
/**
* Testing a component that changes text based on time of day
* ie: good morning, good afternoon & good evening
*/
describe('Greeting', () => {
it('Should update message after time', () => {
jest.useFakeTimers()
setMockDate(new Date('Feb 22, 2021 11:59:00'))
const wrapper = mount(<Greeting />)
const greetingText = wrapper.text()
setMockDate(new Date('Feb 22, 2021 12:00:00'))
jest.advanceTimersByTime(60000)
expect(wrapper.text()).not.toBe(greetingText)
})
})
Related
I have an app that allows users to schedule tasks. Users can set a schedule (schedule, an rrule string) as well as a timezone (scheduleTimeZone, a string e.g. Asia/Dubai).
I am trying to write a function (getNextRunAt) that gets the next occurrence of the task at a UTC date and store this in my Postgres DB as a timestamptz field.
I'm struggling to account for DST, lots of the results are an hour or even a day off.
Here's the function (in TypeScript):
import RRule from 'rrule';
import moment from 'moment-timezone';
const getNextRunAt = ({
schedule,
scheduleTimeZone,
}: {
schedule?: string | null;
scheduleTimeZone?: string | null;
}): Date | undefined => {
if (!schedule) {
return undefined;
}
const options = RRule.parseString(schedule);
if (scheduleTimeZone) {
options.tzid = scheduleTimeZone;
}
const dtstart = moment.utc().toDate();
const rule = new RRule({ ...options, dtstart, count: 1 });
const dates = rule.all();
let date = dates[0];
if (scheduleTimeZone && moment(date).tz(scheduleTimeZone).isDST()) {
date = moment(date).subtract(1, 'hour').toDate();
}
return date;
};
export default getNextRunAt;
It works for some dates/times/timezones:
Date.now = jest.fn(() => new Date('2021-03-02 10:24:27.000000Z').getTime());
const nextDate = getNextRunAt({
schedule: 'RRULE:INTERVAL=1;BYDAY=MO,TU,WE,TH,SA,FR,SU;BYMINUTE=0;BYHOUR=9;BYSECOND=0;FREQ=DAILY',
scheduleTimeZone: 'America/Los_Angeles',
});
expect(nextDate).toEqual(new Date('2021-03-02 17:00:00.000000Z'));
// WORKS
But not for others:
Date.now = jest.fn(() => new Date('2021-03-02 10:24:27.000000Z').getTime());
const nextDate = getNextRunAt({
schedule: 'RRULE:INTERVAL=1;BYDAY=MO,TU,WE,TH,SA,FR,SU;BYMINUTE=0;BYHOUR=9;BYSECOND=0;FREQ=DAILY',
scheduleTimeZone: 'America/Los_Angeles',
});
expect(nextDate).toEqual(new Date('2021-03-02 17:00:00.000000Z'));
// DOES NOT WORK
Expected: 2021-03-02T17:00:00.000Z
Received: 2021-03-03T17:00:00.000Z
I think that the last case is wrong with a day. But for others solution might look like:
const getNextRunAt = ({ schedule, scheduleTimeZone }: { schedule?: string | null; scheduleTimeZone?: string | null }): Date | undefined => {
if (!schedule) {
return undefined;
}
const options = RRule.parseString(schedule);
const dtstart = moment();
const rule = new RRule({
...options,
dtstart: dtstart.toDate(),
count: 1,
});
const dates = rule.all();
const date = dates[0];
let mDate = moment.tz(date, scheduleTimeZone);
const offset = Math.abs(mDate.utcOffset()) > 16 ? mDate.utcOffset() / 60 : mDate.utcOffset();
if (!mDate.isDST()) {
mDate = mDate.add(1, 'hours');
}
mDate.add(-offset, 'hour');
return mDate.toDate();};
I want to select the data before or after a certain date in sqlite using Typeorm where date is in milliseconds.
Here is my Query Builder:
const qb = getConnection()
.getRepository(Post)
.createQueryBuilder('p')
.where('createdAt < :cursor', {
cursor: "1624809058000"
})
.getMany()
We have to pass the date in this format: "yyyy-mm-dd HH:MM:ss.l" Example: "2000-01-01 00:00:00.000"
So, we have to convert the date in this format.
First solution
const chars = { T: ' ', Z: '' }
const dateInMilliseconds = "1624809058000"
const qb = getConnection()
.getRepository(Post)
.createQueryBuilder('p')
.where('createdAt < :cursor', {
cursor: new Date(parseInt(dateInMilliseconds))
.toISOString()
.replace(/[TZ]/g, ch => chars[ch])
})
.getMany()
Second solution
import { LessThan, MoreThan } from 'typeorm'
import { format } from 'date-fns'
// TypeORM Query Operators
const MoreThanDate = (date: Date) => MoreThan(format(date, 'yyyy-mm-dd HH:MM:SS'))
const LessThanDate = (date: Date) => LessThan(format(date, 'yyyy-mm-dd HH:MM:SS'))
const dateInMilliseconds = "1624809058000"
const posts = await Post.find({
createdAt: MoreThanDate(new Date(dateInMilliseconds))
})
Third solution
import { Between } from 'typeorm';
import { addYears, subYears } from 'date-fns';
// TypeORM Query Operators
const MoreThanDate = (date: Date) => Between(date, addYears(date, 100));
const LessThanDate = (date: Date) => Between(subYears(date, 100), date);
const dateInMilliseconds = "1624809058000"
const posts = await Post.find({
where: {
createdAt: MoreThanDate(new Date(dateInMillisecons)),
},
});
When I write tests to run in playwright, I would like to be able to set the date that the browser believes it to be at the start of the test. Is there a way to achieve this using playwright?
Way easier solution with
page.evaluate(() => {
Date.now = () => {
return 1609477200000; //Jan 1, 2021
};
});
You can use mocking tools like TimeShift.
This piece of code would inject the timeshift library and then then the date to Feb 02 2012:
const playwright = require("playwright");
(async () => {
let removed = false;
const browser = await playwright["chromium"].launch({headless: false});
const page = await browser.newPage();
await page.setContent('Test me<span></span>');
await page.click('a');
console.log(await page.innerText('span'));
await page.addScriptTag({ url: 'https://cdn.jsdelivr.net/npm/timeshift-js#1.1.1/timeshift.js'});
await page.evaluate(() => {
Date = TimeShift.Date;
TimeShift.setTime(1328230923000);
});
await page.click('a');
console.log(await page.innerText('span'));
browser.close();
})();
Output:
Fri Sep 18 2020 15:06:58 GMT-0300 (Argentina Standard Time)
Thu Feb 02 2012 22:02:03 GMT-0300
Version without tick functionality:
page.on('load', () => {
page.evaluate(() => {
const OriginalDate = window.Date;
class MockDate extends OriginalDate {
static currentDate = '18 May 2018 12:00 UTC';
static currentTimeStamp = (new OriginalDate(MockDate.currentDate)).getTime();
constructor(...args) {
const params = (args && args.length) ? args : [MockDate.currentTimeStamp];
super(...params);
}
static [Symbol.hasInstance](instance: Date) {
return typeof instance.getDate === 'function';
}
static now() {
return MockDate.currentTimeStamp;
}
}
window.Date = MockDate;
});
});
await page.goto(yourUrl);
Version with tick:
page.on('load', () => {
page.evaluate(() => {
const OriginalDate = window.Date;
class MockDate extends OriginalDate {
static currentDate = '18 May 2018 12:00 UTC';
static currentTimeStamp = (new OriginalDate(MockDate.currentDate)).getTime();
static originalNow = OriginalDate.now();
constructor(...args) {
const params = (args && args.length) ? args : [(MockDate.currentTimeStamp + MockDate.getTick())];
super(...params);
}
static [Symbol.hasInstance](instance: Date) {
return typeof instance.getDate === 'function';
}
static getTick() {
return OriginalDate.now() - MockDate.originalNow;
}
static now() {
return MockDate.currentTimeStamp + MockDate.getTick();
}
}
window.Date = MockDate;
});
});
await page.goto(yourUrl);
I have found one simple solution in a GitHub Issue
async clock(value) {
const date = new Date(value).valueOf();
await page.addInitScript(`{
// Extend Date constructor to default to fakeNow
Date = class extends Date {
constructor(...args) {
if (args.length === 0) {
super(${date.valueOf()});
} else {
super(...args);
}
}
}
// Override Date.now() to start from fakeNow
const __DateNowOffset =${date.valueOf()} - Date.now();
const __DateNow = Date.now;
Date.now = () => __DateNow() + __DateNowOffset;
}`);
}
await clock("2022/01/01")
I am trying to refactor some ugly code in a Google Apps Script web application so that it uses async / await.
It uses the google.script.url.getLocation client-side, to pull URL parameters and then send them off to other async functions.
There must be a way to do this elegantly.
var doSomeAsyncShit =()=> {
google.script.url.getLocation(function (location) {
var rid = (location.parameter.rid) ? location.parameter.rid : defaultReportID;
var uid = (location.parameter.uid) ? location.parameter.uid : defaultUserID;
console.log (((location.parameter.rid) ? "Report #" : "Default Report ID #")+rid);
console.log (((location.parameter.uid) ? "User #" : "Default User ID #" )+uid);
google.script.run.withSuccessHandler(paintReport).returnJSON(rid);
google.script.run.withSuccessHandler(getMyReportsList).listMyReports(uid);
});
}
$(function () {
doSomeAsyncShit();
}
It is possible to intercept requests to google api and directly return Promise using Proxy.
Script:
/**
* Revives old client facing google api in apps script web applications
* Directly returns promises for `google.scipt.run` and `google.script.url.getLocation`
* #see https://stackoverflow.com/a/63537867/
*/
(function projectAdrenaline_google() {
const lifeline = {
funcList: [],
excludeList: [
'withSuccessHandler',
'withFailureHandler',
'withUserObject',
'withLogger',
],
get: function(target, prop, rec) {
if (this.excludeList.includes(prop))
//return (...rest) => new Proxy(Reflect.apply(target[prop], target, rest), trap);
throw new TypeError(
`${prop}: This method is deprecated in this custom api`
);
if (this.funcList.includes(prop))
return (...rest) =>
new Promise((res, rej) =>
target
.withSuccessHandler(res)
.withFailureHandler(rej)
[prop](...rest)
);
switch (prop) {
case 'run':
this.funcList = Object.keys(target.run);
break;
case 'getLocation':
return () => new Promise(res => target[prop](res));
}
return new Proxy(Reflect.get(target, prop, rec), lifeline);
},
};
//const superGoogle = new Proxy(google, trap);
//OR overwrite currently loaded google object:
google = new Proxy(google, lifeline);
})();
Example:
const doSomeAsyncStuff = async () => {
const location = await google.script.url.getLocation();
const rid = location.parameter.rid ? location.parameter.rid : defaultReportID;
const uid = location.parameter.uid ? location.parameter.uid : defaultUserID;
//promise
google.script.run.returnJSON(rid).then(paintReport);
//async-await
const reportsList = await google.script.run.listMyReports(uid);
getMyReportsList(reportsList);
};
Alternatively, It is possible to use functions as syntactic sugars. But this requires learning new syntax definitions:
/**
* Syntactic sugar around old callback api returning a promise
*
* #returns {promise} Promise of call from server
* #param {string[]|string} propertyAccesors Array of properties to access
* #param {object[][]} methodAccesors Array of [method_to_access,arguments[]]
* #param {number[]} resRejIdxs 2 Indexes of methodAccesors corresponding to resolve/success and rejection/failure. If omitted promise is resolved immediately.
*/
const GS = (propertyAccesors, methodAccesors, resRejIdxs) =>
new Promise((res, rej) => {
//Boilerplate for type correction
const nestArray = e => (Array.isArray(e) ? e : [e]);
propertyAccesors = nestArray(propertyAccesors);
methodAccesors = nestArray(methodAccesors);
methodAccesors[0] = nestArray(methodAccesors[0]);
if (typeof resRejIdxs !== 'undefined') {
resRejIdxs = Array.isArray(resRejIdxs) ? resRejIdxs : [resRejIdxs];
resRejIdxs[0] && (methodAccesors[resRejIdxs[0]][1] = res);
resRejIdxs[1] && (methodAccesors[resRejIdxs[1]][1] = rej);
} else {
res('Done');
}
//Access properties and call methods
methodAccesors.reduce(
(acc, [method, methodArg]) =>
Array.isArray(methodArg)
? acc[method](...methodArg)
: acc[method](methodArg),
propertyAccesors.reduce(
(acc, currentProp) => acc[currentProp],
google.script
)
);
});
//EXAMPLES:
GS(
'run',
[
['withSuccessHandler', null],
['callServer', [5, 4]], //call server function `callServer` with 2 arguments 5 and 4
['withFailureHandler', null],
],
[0, 2] //0 is withSuccessHandler and 2 is withFailureHandler
).then(alert);
GS('history', [['setChangeHandler', e => console.log(e.location.hash)]]);
GS('url', 'getLocation', 0).then(location => console.log(location.hash));
GS(['host', 'editor'], 'focus');
GS('host', ['setHeight', 50]);
Since a Promise can be constructed with a custom executor function, you can wrap the google.script.url into it and resolve or reject whenever you like. If you then make it a utility function, use await to wait for it to resolve.
Below is a small flexible utility for making google.script.url async-friendly:
/**
* #typedef {{
* hash : string,
* parameter : Object.<string, string>,
* parameters : Object.<string, string[]>
* }} UrlLocationObject
*
* #typedef {{
* callback : function (UrlLocationObject, ...any) : any,
* params : any[]
* }} AsyncUrlOptions
*
* #summary Promise-friendly google.script.url
* #param {AsyncUrlOptions}
* #returns {Promise}
*/
const asyncLocation = ({
callback,
params = [],
}) => {
return new Promise((res, rej) => {
google.script.url.getLocation((loc) => {
try {
const result = callback(loc, ...params);
res(result);
}
catch(error) {
rej(error);
}
});
});
};
Same goes for google.script.run:
/**
* #typedef {{
* funcName : string,
* onFailure : function,
* onSuccess : function,
* params : array
* }} AsyncOptions
*
* #summary v2 of async-friendly google.script.run
* #param {AsyncOptions}
* #returns {Promise}
*/
const asyncGAPIv2 = ({
funcName,
onFailure = console.error,
onSuccess,
params = []
}) => {
return new Promise((res, rej) => {
google.script.run
.withSuccessHandler(data => {
typeof onSuccess === "function" && onSuccess(data);
res(data);
})
.withFailureHandler(error => {
typeof onFailure === "function" && onFailure(error);
rej(error);
})
[funcName].apply(null, params);
});
};
Something like this isn't too bad:
var doSomeAsyncShit = async () => {
let location = await new promise(resolve => google.script.url.getLocation(resolve))
// do stuff with location
}
(async () => {
await doSomeAsyncShit();
// do something after
})()
Even still, you've added complexity, reduced readability and for no good reasom IMHO
I am saving state in my React app via localStorage
const [items, setItem] = useState(() => {
let itemsString = window.localStorage.getItem('items');
if (itemsString) {
try {
return JSON.parse(itemsString);
} catch (e) {
console.error(e);
return [];
}
} else {
return [];
}
})
when I JSON.parse(itemsString) the date in the state has been converted to UTC (because strings/localstorage)
How do I JSON.parse() my state and reinitialize the date string to an object?
e.g. instead of returning 2019-07-19T00:28:03.058Z return Thu Jul 18 2019 20:28:03 GMT-0400 (Eastern Daylight Time) instead
Solution I came up with the help of Aaron's suggestion below.
Store old state. Map over state array, creating new empty object and storing each property in it, store date, convert date to string and pass that into new Date object to instantiate the value back to a date object on page refresh.
const [items, setItem] = useState(() => {
let itemsString = window.localStorage.getItem('items');
if (itemsString) {
try {
const oldState = JSON.parse(itemsString);
const newState = oldState.map(item => {
const date = item.date;
const container = {}
container.id = item.id
container.item = item.item
container.date = new Date(date.toString())
container.cost = item.cost
container.type = item.type
return container;
})
return newState;
} catch (e) {
console.error(e);
return [];
}
} else {
return [];
}
})
After you parse itemString just set the date key to a new Date object
const object = JSON.parse(itemString);
const newState = {...object, date: new Date(object.date)}