From 6b178385235a470fe5f0e4b0daff0f353b3441f4 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Mon, 2 Mar 2020 18:58:34 +0800 Subject: [PATCH 01/48] test: add test for persona db --- src/database/Persona/Persona.db.ts | 7 +- src/database/Persona/consistency.ts | 2 +- src/database/Persona/helpers.ts | 4 +- src/database/__tests__/Persona/Persona.db.ts | 280 +++++++++++++++++++ src/database/__tests__/Persona/helpers.ts | 233 +++++++++++++++ 5 files changed, 521 insertions(+), 5 deletions(-) create mode 100644 src/database/__tests__/Persona/Persona.db.ts create mode 100644 src/database/__tests__/Persona/helpers.ts diff --git a/src/database/Persona/Persona.db.ts b/src/database/Persona/Persona.db.ts index 0cef0cf84708..a9452fac3f57 100644 --- a/src/database/Persona/Persona.db.ts +++ b/src/database/Persona/Persona.db.ts @@ -37,6 +37,7 @@ const db = createDBAccess(() => { }, }) }) +export const createPersonaDBAccess = db export type FullPersonaDBTransaction = IDBPSafeTransaction< PersonaDB, ['personas', 'profiles'], @@ -186,7 +187,7 @@ export async function updatePersonaDB( ...old, ...nextRecord, linkedProfiles: nextLinkedProfiles, - updatedAt: new Date(), + updatedAt: nextRecord.updatedAt ?? new Date(), }) await t.objectStore('personas').put(next) MessageCenter.emit('personaUpdated', undefined) @@ -202,8 +203,8 @@ export async function createOrUpdatePersonaDB( return createPersonaDB( { ...record, - createdAt: new Date(), - updatedAt: new Date(), + createdAt: record.createdAt ?? new Date(), + updatedAt: record.updatedAt ?? new Date(), linkedProfiles: new IdentifierMap(new Map()), }, t, diff --git a/src/database/Persona/consistency.ts b/src/database/Persona/consistency.ts index be330207babf..12e675d82451 100644 --- a/src/database/Persona/consistency.ts +++ b/src/database/Persona/consistency.ts @@ -121,7 +121,7 @@ async function* checkProfileLink( const designatedPersona = restorePrototype(invalidLinkedPersona, ECKeyIdentifier.prototype) const persona = await t.objectStore('personas').get(designatedPersona.toText()) if (!persona) { - yield { type: Type.One_Way_Link_In_Profile, profile: profile, designatedPersona } + yield { type: Type.One_Way_Link_In_Profile, profile, designatedPersona } } } diff --git a/src/database/Persona/helpers.ts b/src/database/Persona/helpers.ts index 58ef4ca525d6..a2321dc1d803 100644 --- a/src/database/Persona/helpers.ts +++ b/src/database/Persona/helpers.ts @@ -95,7 +95,7 @@ export async function queryProfilesWithQuery(query?: Parameters[0]): Promise { const _ = await queryPersonasDB(query || (_ => true)) @@ -193,6 +193,7 @@ export async function createProfileWithPersona( profileID: ProfileIdentifier, data: LinkedProfileDetails, keys: { + nickname?: string publicKey: JsonWebKey privateKey?: JsonWebKey localKey?: CryptoKey @@ -205,6 +206,7 @@ export async function createProfileWithPersona( updatedAt: new Date(), identifier: ec_id, linkedProfiles: new IdentifierMap(new Map(), ProfileIdentifier), + nickname: keys.nickname, publicKey: keys.publicKey, privateKey: keys.privateKey, localKey: keys.localKey, diff --git a/src/database/__tests__/Persona/Persona.db.ts b/src/database/__tests__/Persona/Persona.db.ts new file mode 100644 index 000000000000..1bf14c55ebd0 --- /dev/null +++ b/src/database/__tests__/Persona/Persona.db.ts @@ -0,0 +1,280 @@ +import uuid from 'uuid/v4' +import { IdentifierMap } from '../../IdentifierMap' +import { ProfileIdentifier, ECKeyIdentifier } from '../../type' +import { + PersonaRecord, + ProfileRecord, + createPersonaDB, + queryPersonaDB, + FullPersonaDBTransaction, + createPersonaDBAccess, + queryPersonasDB, + deletePersonaDB, + updatePersonaDB, + LinkedProfileDetails, + createProfileDB, + queryProfileDB, + queryProfilesDB, + updateProfileDB, + createOrUpdateProfileDB, + deleteProfileDB, + attachProfileDB, + queryPersonaByProfileDB, + queryPersonasWithPrivateKey, + createOrUpdatePersonaDB, + safeDeletePersonaDB, +} from '../../Persona/Persona.db' +import { generate_ECDH_256k1_KeyPair_ByMnemonicWord } from '../../../utils/mnemonic-code' +import { CryptoKeyToJsonWebKey } from '../../../utils/type-transform/CryptoKey-JsonWebKey' +import { deriveLocalKeyFromECDHKey } from '../../../utils/mnemonic-code/localKeyGenerate' +import { createTransaction } from '../../helpers/openDB' + +export async function createPersonaRecord(name: string = uuid(), password: string = uuid()) { + const key = await generate_ECDH_256k1_KeyPair_ByMnemonicWord(password) + const jwkPub = await CryptoKeyToJsonWebKey(key.key.publicKey) + const jwkPriv = await CryptoKeyToJsonWebKey(key.key.privateKey) + const localKey = await deriveLocalKeyFromECDHKey(key.key.publicKey, key.mnemonicRecord.words) + const jwkLocalKey = await CryptoKeyToJsonWebKey(localKey) + const identifier = ECKeyIdentifier.fromJsonWebKey(jwkPub) + + return { + nickname: name, + createdAt: new Date(), + updatedAt: new Date(), + identifier: identifier, + linkedProfiles: new IdentifierMap(new Map(), ProfileIdentifier), + publicKey: jwkPub, + privateKey: jwkPriv, + mnemonic: key.mnemonicRecord, + localKey: jwkLocalKey, + } as PersonaRecord +} + +export async function createProfileRecord( + identifier: ProfileIdentifier = new ProfileIdentifier(uuid(), uuid()), + name: string = uuid(), + password: string = uuid(), +) { + const { localKey, identifier: linkedPersona, createdAt, updatedAt } = await createPersonaRecord(name, password) + + return { + identifier, + nickname: name, + localKey, + linkedPersona, + createdAt, + updatedAt, + } as ProfileRecord +} + +export async function personaDBWriteAccess(action: (t: FullPersonaDBTransaction<'readwrite'>) => Promise) { + await action(createTransaction(await createPersonaDBAccess(), 'readwrite')('profiles', 'personas')) +} + +beforeAll(() => { + // MessageCenter will dispatch events on each tab + // but the mocking query method returns undefined + browser.tabs.query = async () => { + return [] + } +}) + +afterEach(async () => { + await personaDBWriteAccess(async t => { + await t.objectStore('personas').clear() + await t.objectStore('profiles').clear() + }) +}) + +test('createPersonaDB & queryPersonaDB', async () => { + const personaRecord = await createPersonaRecord() + await personaDBWriteAccess(t => createPersonaDB(personaRecord, t)) + expect(await queryPersonaDB(personaRecord.identifier)).toEqual(personaRecord) +}) + +test('queryPersonasDB', async () => { + const personaRecordA = await createPersonaRecord() + const personaRecordB = await createPersonaRecord() + const names = [personaRecordA.nickname, personaRecordB.nickname] + await personaDBWriteAccess(async t => { + await createPersonaDB(personaRecordA, t) + await createPersonaDB(personaRecordB, t) + }) + + const personaRecords = await queryPersonasDB(p => !!(p.nickname && names.includes(p.nickname))) + expect(personaRecords.length).toBe(2) + expect(personaRecords.every(r => names.includes(r.nickname ?? ''))).toBeTruthy() +}) + +test('updatePersonaDB - replace linked profiles', async () => { + const id = uuid() + const personaRecord = await createPersonaRecord(id, id) + const personaRecordNew = await createPersonaRecord(id, id) + personaRecordNew.identifier = personaRecord.identifier + + await personaDBWriteAccess(async t => { + await createPersonaDB(personaRecord, t) + await updatePersonaDB( + personaRecordNew, + { + linkedProfiles: 'replace', + explicitUndefinedField: 'ignore', + }, + t, + ) + }) + expect(await queryPersonaDB(personaRecord.identifier)).toEqual(personaRecordNew) +}) + +test('deletePersonaDB', async () => { + const personaRecord = await createPersonaRecord() + await personaDBWriteAccess(t => createPersonaDB(personaRecord, t)) + expect(await queryPersonaDB(personaRecord.identifier)).toEqual(personaRecord) + + await personaDBWriteAccess(t => deletePersonaDB(personaRecord.identifier, 'delete even with private', t)) + expect(await queryPersonaDB(personaRecord.identifier)).toEqual(null) +}) + +test('queryPersonaByProfileDB', async () => { + const profileRecord = await createProfileRecord() + const personaRecord = await createPersonaRecord() + profileRecord.linkedPersona = personaRecord.identifier + await personaDBWriteAccess(async t => { + await createProfileDB(profileRecord, t) + await createPersonaDB(personaRecord, t) + }) + + expect(await queryPersonaByProfileDB(profileRecord.identifier)).toEqual(personaRecord) +}) + +test('queryPersonasWithPrivateKey', async () => { + const personaRecordA = await createPersonaRecord() + const personaRecordB = await createPersonaRecord() + await personaDBWriteAccess(async t => { + await createPersonaDB(personaRecordA, t) + await createPersonaDB(personaRecordB, t) + }) + const names = (await queryPersonasWithPrivateKey()).map(p => p.nickname) + + expect(names.includes(personaRecordA.nickname)).toBe(true) + expect(names.includes(personaRecordB.nickname)).toBe(true) +}) + +test('createOrUpdatePersonaDB', async () => { + const personaRecord = await createPersonaRecord() + const personaRecordNew = await createPersonaRecord() + const howToMerge = { + linkedProfiles: 'replace', + explicitUndefinedField: 'ignore', + } as Parameters[1] + personaRecordNew.identifier = personaRecord.identifier + + expect(await queryPersonaDB(personaRecord.identifier)).toEqual(null) + + await personaDBWriteAccess(t => createOrUpdatePersonaDB(personaRecord, howToMerge, t)) + expect(await queryPersonaDB(personaRecord.identifier)).toEqual(personaRecord) + + await personaDBWriteAccess(t => createOrUpdatePersonaDB(personaRecordNew, howToMerge, t)) + expect(await queryPersonaDB(personaRecord.identifier)).toEqual(personaRecordNew) +}) + +test('safeDeletePersonaDB', async () => { + const personaRecord = await createPersonaRecord() + const howToMerge = { + linkedProfiles: 'replace', + explicitUndefinedField: 'delete field', + } as Parameters[1] + + await personaDBWriteAccess(t => createPersonaDB(personaRecord, t)) + expect(await queryPersonaDB(personaRecord.identifier)).toEqual(personaRecord) + + personaRecord.linkedProfiles.clear() + await personaDBWriteAccess(async t => { + await updatePersonaDB(personaRecord, howToMerge, t) + await safeDeletePersonaDB(personaRecord.identifier, t) + }) + expect(await queryPersonaDB(personaRecord.identifier)).toEqual(personaRecord) + + personaRecord.privateKey = undefined + await personaDBWriteAccess(async t => { + await updatePersonaDB(personaRecord, howToMerge, t) + await safeDeletePersonaDB(personaRecord.identifier, t) + }) + expect(await queryPersonaDB(personaRecord.identifier)).toEqual(null) +}) + +test('createProfileDB && queryProfileDB', async () => { + const profileRecord = await createProfileRecord() + await personaDBWriteAccess(t => createProfileDB(profileRecord, t)) + expect(await queryProfileDB(profileRecord.identifier)).toEqual(profileRecord) +}) + +test('queryProfilesDB', async () => { + const profileRecordA = await createProfileRecord() + const profileRecordB = await createProfileRecord() + const nicknames = [profileRecordA.nickname, profileRecordB.nickname] + const networkIds = [profileRecordA.identifier.network, profileRecordB.identifier.network] + await personaDBWriteAccess(async t => { + await createProfileDB(profileRecordA, t) + await createProfileDB(profileRecordB, t) + }) + + const profileRecords = await queryProfilesDB(p => networkIds.includes(p.identifier.network)) + expect(profileRecords.length).toBe(2) + expect(profileRecords.every(r => nicknames.includes(r.nickname ?? ''))).toBeTruthy() +}) + +test('updateProfileDB', async () => { + const profileRecord = await createProfileRecord() + const profileRecordNew = await createProfileRecord() + profileRecordNew.identifier = profileRecord.identifier + + await personaDBWriteAccess(async t => { + await createProfileDB(profileRecord, t) + await updateProfileDB(profileRecordNew, t) + }) + + expect(await queryProfileDB(profileRecord.identifier)).toEqual(profileRecordNew) +}) + +test('createOrUpdateProfileDB', async () => { + const profileRecord = await createProfileRecord() + const profileRecordNew = await createProfileRecord() + profileRecordNew.identifier = profileRecord.identifier + + expect(await queryProfileDB(profileRecord.identifier)).toEqual(null) + + await personaDBWriteAccess(t => createOrUpdateProfileDB(profileRecord, t)) + expect(await queryProfileDB(profileRecord.identifier)).toEqual(profileRecord) + + await personaDBWriteAccess(t => createOrUpdateProfileDB(profileRecordNew, t)) + expect(await queryProfileDB(profileRecord.identifier)).toEqual(profileRecordNew) +}) + +test('attachProfileDB && detachProfileDB', async () => { + const profileRecord = await createProfileRecord() + const personaRecord = await createPersonaRecord() + + await personaDBWriteAccess(async t => { + await createPersonaDB(personaRecord, t) + await attachProfileDB( + profileRecord.identifier, + personaRecord.identifier, + { + connectionConfirmState: 'confirmed', + }, + t, + ) + }) + expect((await queryProfileDB(profileRecord.identifier))?.linkedPersona).toEqual(personaRecord.identifier) +}) + +test('deleteProfileDB', async () => { + const profileRecord = await createProfileRecord() + + await personaDBWriteAccess(t => createProfileDB(profileRecord, t)) + expect(await queryProfileDB(profileRecord.identifier)).toEqual(profileRecord) + + await personaDBWriteAccess(t => deleteProfileDB(profileRecord.identifier, t)) + expect(await queryProfileDB(profileRecord.identifier)).toEqual(null) +}) diff --git a/src/database/__tests__/Persona/helpers.ts b/src/database/__tests__/Persona/helpers.ts new file mode 100644 index 000000000000..ff5aba25f3ae --- /dev/null +++ b/src/database/__tests__/Persona/helpers.ts @@ -0,0 +1,233 @@ +import uuid from 'uuid/v4' +import { web3 } from '../../../plugins/Wallet/web3' +import { + createPersonaByMnemonic, + queryPersona, + profileRecordToProfile, + personaRecordToPersona, + queryProfile, + queryProfilesWithQuery, + queryPersonasWithQuery, + renamePersona, + queryPersonaByProfile, + queryPersonaRecord, + queryPublicKey, + queryLocalKey, + queryPrivateKey, + createProfileWithPersona, + deletePersona, +} from '../../Persona/helpers' +import { queryPersonaDB, createProfileDB, createPersonaDB, queryProfileDB } from '../../Persona/Persona.db' +import { createPersonaRecord, createProfileRecord, personaDBWriteAccess } from './Persona.db' +import { storeAvatarDB } from '../../avatar' +import { JsonWebKeyToCryptoKey, getKeyParameter } from '../../../utils/type-transform/CryptoKey-JsonWebKey' + +beforeAll(() => { + // MessageCenter will dispatch events on each tab + // but default query method returns undefined + browser.tabs.query = async () => { + return [] + } +}) + +afterEach(async () => { + await personaDBWriteAccess(async t => { + await t.objectStore('personas').clear() + await t.objectStore('profiles').clear() + }) +}) + +test('profileRecordToProfile', async () => { + const profileRecord = await createProfileRecord() + await storeAvatarDB(profileRecord.identifier, new ArrayBuffer(20)) + + const profile = await profileRecordToProfile(profileRecord) + expect(profile.avatar).toBe('data:image/png;base64,AAAAAAAAAAAAAAAAAAAAAAAAAAA=') + expect(profile.linkedPersona?.identifier).toEqual(profileRecord.linkedPersona) +}) + +test('personaRecordToPersona', async () => { + const personaRecord = await createPersonaRecord() + const persona = personaRecordToPersona(personaRecord) + + expect(persona.hasPrivateKey).toBe(true) + expect(persona.fingerprint).toBe(personaRecord.identifier.compressedPoint) +}) + +test('queryProfile', async () => { + const profileRecord = await createProfileRecord() + const fake = await queryProfile(profileRecord.identifier) + expect(fake.avatar).toBe(undefined) + expect(fake.linkedPersona).toBe(undefined) + + await storeAvatarDB(profileRecord.identifier, new ArrayBuffer(20)) + await personaDBWriteAccess(t => createProfileDB(profileRecord, t)) + + const real = await queryProfile(profileRecord.identifier) + expect(real.avatar).toBe('data:image/png;base64,AAAAAAAAAAAAAAAAAAAAAAAAAAA=') + expect(real.linkedPersona?.identifier).toEqual(profileRecord.linkedPersona) +}) + +test('queryPersona', async () => { + const personaRecord = await createPersonaRecord() + const fake = await queryPersona(personaRecord.identifier) + expect(fake.hasPrivateKey).toBe(false) + expect(fake.fingerprint).toBe(personaRecord.identifier.compressedPoint) + + await personaDBWriteAccess(t => createPersonaDB(personaRecord, t)) + + const real = await queryPersona(personaRecord.identifier) + expect(real.hasPrivateKey).toBe(true) + expect(real.fingerprint).toBe(personaRecord.identifier.compressedPoint) +}) + +test('queryProfilesWithQuery', async () => { + const profileRecordA = await createProfileRecord() + const profileRecordB = await createProfileRecord() + const names = [profileRecordA.nickname, profileRecordB.nickname] + + await personaDBWriteAccess(async t => { + await createProfileDB(profileRecordA, t) + await createProfileDB(profileRecordB, t) + }) + + const profiles = await queryProfilesWithQuery(({ nickname }) => names.includes(nickname)) + expect(profiles.every(p => names.includes(p.nickname))).toBe(true) +}) + +test('queryPersonasWithQuery', async () => { + const personaRecordA = await createPersonaRecord() + const personaRecordB = await createPersonaRecord() + const names = [personaRecordA.nickname, personaRecordB.nickname] + + await personaDBWriteAccess(async t => { + await createPersonaDB(personaRecordA, t) + await createPersonaDB(personaRecordB, t) + }) + + const personas = await queryPersonasWithQuery(({ nickname }) => names.includes(nickname)) + expect(personas.every(p => names.includes(p.nickname))).toBe(true) +}) + +test('deletePersona', async () => { + const personaRecord = await createPersonaRecord() + await personaDBWriteAccess(t => createPersonaDB(personaRecord, t)) + expect(await queryPersonaDB(personaRecord.identifier)).toEqual(personaRecord) + + await deletePersona(personaRecord.identifier, 'delete even with private') + expect(await queryPersonaDB(personaRecord.identifier)).toEqual(null) +}) + +test('renamePersona', async () => { + const name = uuid() + const personaRecord = await createPersonaRecord() + + await personaDBWriteAccess(t => createPersonaDB(personaRecord, t)) + await renamePersona(personaRecord.identifier, name) + expect((await queryPersonaDB(personaRecord.identifier))?.nickname).toBe(name) +}) + +test('queryPersonaByProfile', async () => { + const profileRecord = await createProfileRecord() + const personaRecord = await createPersonaRecord() + profileRecord.linkedPersona = personaRecord.identifier + personaRecord.linkedProfiles.set(profileRecord.identifier, { + connectionConfirmState: 'confirmed', + }) + await personaDBWriteAccess(async t => { + await createProfileDB(profileRecord, t) + await createPersonaDB(personaRecord, t) + }) + expect(await queryPersonaByProfile(profileRecord.identifier)).toEqual(personaRecordToPersona(personaRecord)) +}) + +test('queryPersonaRecord', async () => { + const profileRecord = await createProfileRecord() + const personaRecord = await createPersonaRecord() + profileRecord.linkedPersona = personaRecord.identifier + personaRecord.linkedProfiles.set(profileRecord.identifier, { + connectionConfirmState: 'confirmed', + }) + await personaDBWriteAccess(async t => { + await createProfileDB(profileRecord, t) + await createPersonaDB(personaRecord, t) + }) + expect(await queryPersonaRecord(profileRecord.identifier)).toEqual(personaRecord) + expect(await queryPersonaRecord(personaRecord.identifier)).toEqual(personaRecord) +}) + +test('queryPublicKey', async () => { + const profileRecord = await createProfileRecord() + const personaRecord = await createPersonaRecord() + const publicKey = await JsonWebKeyToCryptoKey(personaRecord.publicKey, ...getKeyParameter('ecdh')) + profileRecord.linkedPersona = personaRecord.identifier + personaRecord.linkedProfiles.set(profileRecord.identifier, { + connectionConfirmState: 'confirmed', + }) + await personaDBWriteAccess(async t => { + await createProfileDB(profileRecord, t) + await createPersonaDB(personaRecord, t) + }) + expect(await queryPublicKey(profileRecord.identifier)).toEqual(publicKey) + expect(await queryPublicKey(personaRecord.identifier)).toEqual(publicKey) +}) + +test('queryPrivateKey', async () => { + const profileRecord = await createProfileRecord() + const personaRecord = await createPersonaRecord() + const privateKey = await JsonWebKeyToCryptoKey(personaRecord.privateKey!, ...getKeyParameter('ecdh')) + profileRecord.linkedPersona = personaRecord.identifier + personaRecord.linkedProfiles.set(profileRecord.identifier, { + connectionConfirmState: 'confirmed', + }) + await personaDBWriteAccess(async t => { + await createProfileDB(profileRecord, t) + await createPersonaDB(personaRecord, t) + }) + expect(await queryPrivateKey(profileRecord.identifier)).toEqual(privateKey) + expect(await queryPrivateKey(personaRecord.identifier)).toEqual(privateKey) +}) + +test('createPersonaByMnemonic & createPersonaByJsonWebKey', async () => { + // getBalance will be called in the event chain reaction + web3.eth.getBalance = async () => '0' + + const identifier = await createPersonaByMnemonic('test', 'test') + const persona = await queryPersonaDB(identifier) + expect(persona?.identifier).toEqual(identifier) + expect(persona?.nickname).toEqual('test') + expect(persona?.mnemonic?.parameter.withPassword).toEqual(true) +}) + +test('createProfileWithPersona', async () => { + const profileRecord = await createProfileRecord() + const personaRecord = await createPersonaRecord() + await createProfileWithPersona( + profileRecord.identifier, + { + connectionConfirmState: 'confirmed', + }, + personaRecord, + ) + profileRecord.linkedPersona = personaRecord.identifier + personaRecord.linkedProfiles.set(profileRecord.identifier, { + connectionConfirmState: 'confirmed', + }) + expect((await queryProfileDB(profileRecord.identifier))?.linkedPersona).toEqual(personaRecord.identifier) + expect((await queryPersonaDB(personaRecord.identifier))?.identifier).toEqual(personaRecord.identifier) +}) + +test('queryLocalKey', async () => { + const profileRecord = await createProfileRecord() + const personaRecord = await createPersonaRecord() + profileRecord.linkedPersona = personaRecord.identifier + personaRecord.linkedProfiles.set(profileRecord.identifier, { + connectionConfirmState: 'confirmed', + }) + await personaDBWriteAccess(async t => { + await createProfileDB(profileRecord, t) + await createPersonaDB(personaRecord, t) + }) + expect(await queryLocalKey(profileRecord.identifier)).toEqual(profileRecord.localKey) + expect(await queryLocalKey(personaRecord.identifier)).toEqual(personaRecord.localKey) +}) From 40d1143fc8117f3d0c2d0a15d2e25789c89ac0b8 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Fri, 28 Feb 2020 18:23:55 +0800 Subject: [PATCH 02/48] refactor: use captured input --- .../InjectedComponents/CommentBox.tsx | 8 +-- .../__tests__/hooks/useCaptureEvents.tsx | 14 +++-- src/utils/hooks/useCapturedEvents.ts | 57 ++++++++++--------- 3 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/components/InjectedComponents/CommentBox.tsx b/src/components/InjectedComponents/CommentBox.tsx index ecea46936593..5bbd60c6995a 100644 --- a/src/components/InjectedComponents/CommentBox.tsx +++ b/src/components/InjectedComponents/CommentBox.tsx @@ -36,14 +36,14 @@ export interface CommentBoxProps { } export function CommentBox(props: CommentBoxProps) { const classes = useStyles() - const [binder, inputRef] = useCapturedInput(() => {}) + const [binder, inputRef, node] = useCapturedInput(() => {}) const { t } = useI18N() useEffect( binder(['keypress'], e => { - if (!inputRef.current) return + if (!node) return if (e.key === 'Enter') { - props.onSubmit(inputRef.current.value) - inputRef.current.value = '' + props.onSubmit(node.value) + node.value = '' } }), ) diff --git a/src/utils/__tests__/hooks/useCaptureEvents.tsx b/src/utils/__tests__/hooks/useCaptureEvents.tsx index a209267acb12..4abc8fcee597 100644 --- a/src/utils/__tests__/hooks/useCaptureEvents.tsx +++ b/src/utils/__tests__/hooks/useCaptureEvents.tsx @@ -4,7 +4,7 @@ import Adapter from 'enzyme-adapter-react-16' configure({ adapter: new Adapter() }) import React, { createRef, RefObject } from 'react' -import { renderHook } from '@testing-library/react-hooks' +import { renderHook, act } from '@testing-library/react-hooks' import { useCapturedInput, captureEevnts } from '../../hooks/useCapturedEvents' const containerRef: RefObject = createRef() @@ -20,7 +20,8 @@ beforeEach(() => { test('invoke callback with input value', () => { const inputSpy = jasmine.createSpy() - renderHook(() => useCapturedInput(inputSpy, [], inputRef)) + const [_, updateInputNode] = renderHook(() => useCapturedInput(inputSpy, [])).result.current + act(() => updateInputNode(inputRef.current)) inputRef.current!.dispatchEvent(new CustomEvent('input', { bubbles: true })) expect(inputSpy.calls.argsFor(0)).toStrictEqual(['']) @@ -42,7 +43,8 @@ for (const name of captureEevnts) { }) test(`capture event: ${name}`, () => { const containerSpy = jasmine.createSpy() - renderHook(() => useCapturedInput(() => {}, [], inputRef)) + const [_, updateInputNode] = renderHook(() => useCapturedInput(() => {}, [])).result.current + act(() => updateInputNode(inputRef.current)) containerRef.current!.addEventListener(name, containerSpy) inputRef.current!.dispatchEvent(new CustomEvent(name, { bubbles: true })) @@ -52,12 +54,14 @@ for (const name of captureEevnts) { }) test(`remove listener: ${name}`, () => { const containerSpy = jasmine.createSpy() - containerRef.current!.addEventListener(name, containerSpy) inputRef.current!.dispatchEvent(new CustomEvent(name, { bubbles: true })) expect(containerSpy.calls.count()).toBe(1) - const hook = renderHook(() => useCapturedInput(() => {}, [], inputRef)) + const hook = renderHook(() => useCapturedInput(() => {}, [])) + const [_, updateInputNode] = hook.result.current + act(() => updateInputNode(inputRef.current)) + inputRef.current!.dispatchEvent(new CustomEvent(name, { bubbles: true })) expect(containerSpy.calls.count()).toBe(1) diff --git a/src/utils/hooks/useCapturedEvents.ts b/src/utils/hooks/useCapturedEvents.ts index 09e5a952aa1d..bcf0566e5d27 100644 --- a/src/utils/hooks/useCapturedEvents.ts +++ b/src/utils/hooks/useCapturedEvents.ts @@ -1,6 +1,5 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { useEffect, useCallback } from 'react' -import { or } from '../../components/custom-ui-helper' import React from 'react' export const captureEevnts: (keyof HTMLElementEventMap)[] = [ @@ -19,37 +18,43 @@ export const captureEevnts: (keyof HTMLElementEventMap)[] = [ 'change', ] +function binder( + node: HTMLInputElement | null, + keys: T[], + fn: (e: HTMLElementEventMap[T]) => void, +) { + let current: HTMLInputElement | null + const bind = (input: HTMLInputElement) => keys.forEach(k => input.addEventListener(k, fn, true)) + const unbind = (input: HTMLInputElement) => keys.forEach(k => input.removeEventListener(k, fn, true)) + return () => { + if (!node) return + current = node + bind(current) + return () => { + if (!current) return + unbind(current) + current = null + } + } +} + /** * ! Call this hook inside Shadow Root! */ -export function useCapturedInput( - onChange: (newVal: string) => void, - deps: any[] = [], - _ref?: React.MutableRefObject, -) { - const ref = or(_ref, React.useRef(null)) +export function useCapturedInput(onChange: (newVal: string) => void, deps: any[] = []) { + const [node, setNode] = React.useState(null) + const ref = useCallback((nextNode: HTMLInputElement | null) => setNode(nextNode), []) const stop = useCallback((e: Event) => e.stopPropagation(), deps) const use = useCallback( (e: Event) => onChange((e.currentTarget as HTMLInputElement)?.value ?? (e.target as HTMLInputElement)?.value), [onChange].concat(deps), ) - function binder(keys: T[], fn: (e: HTMLElementEventMap[T]) => void) { - let current: HTMLInputElement | null - const bind = (input: HTMLInputElement) => keys.forEach(k => input.addEventListener(k, fn, true)) - const unbind = (input: HTMLInputElement) => keys.forEach(k => input.removeEventListener(k, fn, true)) - - return () => { - if (!ref.current) return - current = ref.current - bind(current) - return () => { - if (!current) return - unbind(current) - current = null - } - } - } - useEffect(binder(['input'], use), [ref.current].concat(deps)) - useEffect(binder(captureEevnts, stop), [ref.current].concat(deps)) - return [binder, ref] as const + useEffect(binder(node, ['input'], use), [node].concat(deps)) + useEffect(binder(node, captureEevnts, stop), [node].concat(deps)) + return [ + (keys: T[], fn: (e: HTMLElementEventMap[T]) => void) => + binder(node, keys, fn), + ref, + node, + ] as const } From 12c7cffe6e1fa0e662d5b1d6c6575fcc9b2598d4 Mon Sep 17 00:00:00 2001 From: Septs Date: Tue, 3 Mar 2020 18:41:53 +0800 Subject: [PATCH 03/48] chore: update ignore files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 3d142b8a8c40..211e2b3d5b3c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,11 +14,13 @@ public/polyfill # testing /coverage +/junit.xml # production /build /dist /storybook-static +/Maskbook.*.zip # misc .DS_Store From 1bbaa8f786f3a38bc412e168f8984a54112aa103 Mon Sep 17 00:00:00 2001 From: Septs Date: Tue, 3 Mar 2020 18:43:27 +0800 Subject: [PATCH 04/48] chore: target esnext in webpack ts-loader --- .circleci/config.yml | 2 +- package.json | 2 +- tsconfig.json | 2 +- webpack.config.js | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a5b04e20cae..edab593386ef 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,7 +41,7 @@ jobs: - restore_workspace - run: name: 'TypeScript type check' - command: yarn tsc -p tsconfig_cjs.json --noEmit + command: yarn tsc --noEmit eslint: executor: maskbook_node steps: diff --git a/package.json b/package.json index 8d3ce8e831b8..db6625601559 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "install": "node scripts/postinstall.js", "lint": "eslint src/ --ext .ts,.tsx --fix", "lint:report": "eslint src/ --ext .ts,.tsx --format junit -o reports/junit/eslint-results.xml", - "lint:typecoverage": "type-coverage -p tsconfig_cjs.json --strict --ignore-catch --detail --at-least 98.5", + "lint:typecoverage": "type-coverage --strict --ignore-catch --detail --at-least 98.5", "start": "webpack-dev-server --mode development", "start:android": "webpack-dev-server --firefox-android --mode development", "start:chromium": "webpack-dev-server --chromium --mode development", diff --git a/tsconfig.json b/tsconfig.json index c684853ca76e..d1ac6fab31f5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "target": "ES2019" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, - "module": "ESNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, + "module": "CommmonJS" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, "lib": ["DOM", "DOM.Iterable", "ES2019"] /* Specify library files to be included in the compilation. */, "allowJs": true /* Allow javascript files to be compiled. */, // "checkJs": true, /* Report errors in .js files. */ diff --git a/webpack.config.js b/webpack.config.js index e5717c8448a9..afc9393d67a9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -303,6 +303,7 @@ function addTSLoader() { options: { transpileOnly: true, compilerOptions: { + module: 'esnext', jsx: 'react', noEmit: false, }, From 37089de7df4881f5ad0199f01393bbaa570b0189 Mon Sep 17 00:00:00 2001 From: Septs Date: Tue, 3 Mar 2020 18:51:33 +0800 Subject: [PATCH 05/48] refactor: post-install, migrate to typescript --- package.json | 4 ++-- scripts/postinstall.js | 29 ----------------------------- scripts/postinstall.ts | 10 ++++++++++ 3 files changed, 12 insertions(+), 31 deletions(-) delete mode 100644 scripts/postinstall.js create mode 100644 scripts/postinstall.ts diff --git a/package.json b/package.json index db6625601559..21796908d87f 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "build:gecko": "webpack --mode production --firefox-gecko", "build:ios": "webpack --mode production --wk-webview", "compile:contracts": "./scripts/contracts.sh", - "install": "node scripts/postinstall.js", + "install": "ts-node scripts/postinstall.ts", "lint": "eslint src/ --ext .ts,.tsx --fix", "lint:report": "eslint src/ --ext .ts,.tsx --format junit -o reports/junit/eslint-results.xml", "lint:typecoverage": "type-coverage --strict --ignore-catch --detail --at-least 98.5", @@ -25,7 +25,7 @@ "storybook": "start-storybook -p 9009 -s public --quiet", "test": "jest --watch", "test:ci": "jest --ci --collectCoverage=true --reporters=default --reporters=jest-junit", - "uninstall": "node scripts/postinstall.js" + "uninstall": "ts-node scripts/postinstall.ts" }, "husky": { "hooks": { diff --git a/scripts/postinstall.js b/scripts/postinstall.js deleted file mode 100644 index 8f8cda55419b..000000000000 --- a/scripts/postinstall.js +++ /dev/null @@ -1,29 +0,0 @@ -const { spawn } = require('./spawn') -const path = require('path') - -const base = path.join(__dirname, '../') -process.chdir(base) -;(async () => { - if (process.argv.indexOf('--upgrade') !== -1) await spawn('yarn', ['upgrade', '@holoflows/kit']) - process.chdir('node_modules/@holoflows/kit') - await spawn('yarn', ['install']) - try { - /** - * For unknown reason, first time build will raise an exception. But if we build it twice, problem will be fixed - * - * src/Extension/AutomatedTabTask.ts:119:17 - - * error TS2742: The inferred type of 'AutomatedTabTask' cannot be named - * without a reference to '@holoflows/kit/node_modules/csstype'. - * This is likely not portable. - * A type annotation is necessary. - * 119 export function AutomatedTabTask PromiseLike>>( - * ~~~~~~~~~~~~~~~~ - */ - await spawn('yarn', ['build:tsc']) - await spawn('yarn', ['build:rollup']) - } catch (e) { - console.log('Build failed, retry one more time.') - await spawn('yarn', ['build:tsc']) - await spawn('yarn', ['build:rollup']) - } -})() diff --git a/scripts/postinstall.ts b/scripts/postinstall.ts new file mode 100644 index 000000000000..1abc8776aff7 --- /dev/null +++ b/scripts/postinstall.ts @@ -0,0 +1,10 @@ +import { execFileSync } from 'child_process' +import path from 'path' + +const stdio = [process.stdin, process.stdout, process.stderr] +const cwd = path.join(__dirname, '..', 'node_modules', '@holoflows', 'kit') +const yarn = (...args: string[]) => execFileSync('yarn', args, { cwd, stdio }) + +yarn('install') +yarn('build:tsc') +yarn('build:rollup') From cdf4906942965e27ff24c12449a019494cc43ae0 Mon Sep 17 00:00:00 2001 From: Septs Date: Tue, 3 Mar 2020 18:52:02 +0800 Subject: [PATCH 06/48] refactor: ci-build, migrate to typescript --- .circleci/config.yml | 2 +- package.json | 1 + scripts/ci-build.js | 72 -------------------------------------------- scripts/ci-build.ts | 38 +++++++++++++++++++++++ 4 files changed, 40 insertions(+), 73 deletions(-) delete mode 100644 scripts/ci-build.js create mode 100644 scripts/ci-build.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index edab593386ef..dfe61f829089 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -91,7 +91,7 @@ jobs: command: sudo apt-get install zip - run: name: Build Maskbook - command: node ./scripts/ci-build.js + command: yarn build-ci - store_artifacts: path: Maskbook.base.zip destination: /Maskbook.base.zip diff --git a/package.json b/package.json index 21796908d87f..c742386df7a2 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "license": "AGPL-3.0-or-later", "scripts": { "build": "webpack --mode production", + "build-ci": "ts-node scripts/ci-build.ts", "build-storybook": "build-storybook -s public --quiet", "build:base": "webpack --mode production", "build:chromium": "webpack --mode production --chromium", diff --git a/scripts/ci-build.js b/scripts/ci-build.js deleted file mode 100644 index d9fe0181ea74..000000000000 --- a/scripts/ci-build.js +++ /dev/null @@ -1,72 +0,0 @@ -const { spawn } = require('./spawn') -const path = require('path') - -const base = path.join(__dirname, '../') -process.chdir(base) -async function main() { - const prepareCommands = getCommands(` - `) - for (const command of prepareCommands) { - await spawn(command) - } - const currentBranch = (await getCurrentGitBranchName()).toLowerCase() - for (const command of getCommands(buildTypes(currentBranch))) { - console.log('executing', command) - await spawn(command) - } - process.exit(0) -} -main().catch(e => { - console.error(e) - process.exit(1) -}) - -/** - * @param {string} branchName - */ -function buildTypes(branchName) { - if (branchName.match(/full/) || branchName === 'master') - return getBuildCommand(['base', 'chromium', 'firefox', 'gecko', 'iOS']) - if (branchName.match(/ios/)) return getBuildCommand(['iOS']) - if (branchName.match(/android|gecko/)) return getBuildCommand(['firefox', 'gecko']) - return getBuildCommand(['base', 'chromium', 'firefox']) -} - -/** - * @param {('base' | 'iOS' | 'chromium' | 'firefox' | 'gecko')[]} platforms - */ -function getBuildCommand(platforms) { - return platforms - .sort() - .map(generateCommand) - .join('\n') - /** @param {('base' | 'iOS' | 'chromium' | 'firefox' | 'gecko')} type */ - function generateCommand(type) { - if (type === 'chromium' && platforms.indexOf('base') !== -1) { - // chromium doesn't have it's own changes yet. - // just copying base version is acceptable - return 'cp Maskbook.base.zip Maskbook.chromium.zip' - } - return ` - echo "Building for target ${type}" - yarn build:${type.toLowerCase()} - bash -c "cd build && zip -r ../Maskbook.${type}.zip ./*" - rm -rf build - ` - } -} -async function getCurrentGitBranchName() { - const gitcmd = getCommands(`git rev-parse --abbrev-ref HEAD`)[0] - const branch = await spawn(gitcmd, null, { stdio: 'pipe' }) - return branch.split('\n')[0] -} - -/** - * @param {string} string - */ -function getCommands(string) { - return string - .split('\n') - .map(x => x.trimLeft()) - .filter(x => x && !x.startsWith('#')) -} diff --git a/scripts/ci-build.ts b/scripts/ci-build.ts new file mode 100644 index 000000000000..a504ed5eb935 --- /dev/null +++ b/scripts/ci-build.ts @@ -0,0 +1,38 @@ +import { execFileSync, ExecFileSyncOptions } from 'child_process' +import path from 'path' +import git from '@nice-labs/git-rev' + +const cwd = path.join(__dirname, '..') +const BUILD_PATH = path.join(cwd, 'build') + +const exec = (command: string, args?: ReadonlyArray, options?: ExecFileSyncOptions) => { + options = { cwd, stdio: [process.stdin, process.stdout, process.stderr], ...options } + execFileSync(command, args, options) +} + +function buildTypes(name: string): string[] { + if (/full/.test(name) || name === 'master') { + return ['base', 'chromium', 'firefox', 'gecko', 'iOS'] + } else if (/ios/.test(name)) { + return ['iOS'] + } else if (/android|gecko/.test(name)) { + return ['firefox', 'gecko'] + } else { + return ['base', 'chromium', 'firefox'] + } +} + +const branch = git.branchName() +const types = buildTypes(branch.toLowerCase()) +console.log(`Branch: ${branch}`) +for (const type of types) { + if (type === 'chromium' && types.includes('base')) { + // chromium doesn't have it's own changes yet. + // just copying base version is acceptable + exec('cp', ['Maskbook.base.zip', 'Maskbook.chromium.zip']) + } + console.log(`Building for target: ${type}`) + exec('yarn', [`build:${type.toLowerCase()}`]) + exec('zip', ['-r', `../Maskbook.${type}.zip`, '.'], { cwd: BUILD_PATH }) + exec('rm', ['-rf', BUILD_PATH]) +} From bbad394ee4b53c1aea36adae487768ad39c1b6a6 Mon Sep 17 00:00:00 2001 From: Septs Date: Tue, 3 Mar 2020 18:52:20 +0800 Subject: [PATCH 07/48] chore: remove scripts/spawn.js --- scripts/spawn.js | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 scripts/spawn.js diff --git a/scripts/spawn.js b/scripts/spawn.js deleted file mode 100644 index b2839e1e9cc7..000000000000 --- a/scripts/spawn.js +++ /dev/null @@ -1,39 +0,0 @@ -const { spawn } = require('child_process') - -process.on('unhandledRejection', e => { - process.exit(1) -}) -/** - * - * @param {string} command Command - * @param {string[]} args Arguments - * @param {import('child_process').SpawnOptionsWithoutStdio} options Options - */ -function spawnAsync(command, args, options = {}) { - const child = spawn(command, args, { stdio: 'inherit', shell: true, ...options }) - let stdout = '' - let stderr = '' - if (child.stdout) { - child.stdout.addListener('data', data => (stdout += data.toString())) - } - if (child.stderr) { - child.stderr.addListener('data', data => (stderr += data.toString())) - } - return new Promise(function(resolve, reject) { - child.addListener('error', e => { - child.kill() - reject(e) - }) - child.addListener('exit', code => { - if (code === 0) return resolve(stdout) - const error = new Error('Child exited with code: ' + code) - console.log(stdout) - console.error(stderr) - reject(error) - }) - }) -} - -module.exports = { - spawn: spawnAsync, -} From 98b7c4d64f5a6f52fb0e96870ad559e796399fd8 Mon Sep 17 00:00:00 2001 From: Septs Date: Tue, 3 Mar 2020 18:57:22 +0800 Subject: [PATCH 08/48] fix: typo --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index d1ac6fab31f5..03e02c4a6f0e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "target": "ES2019" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, - "module": "CommmonJS" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, + "module": "CommonJS" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, "lib": ["DOM", "DOM.Iterable", "ES2019"] /* Specify library files to be included in the compilation. */, "allowJs": true /* Allow javascript files to be compiled. */, // "checkJs": true, /* Report errors in .js files. */ From bfc912cc42c381a1d16a38614476253eb867d874 Mon Sep 17 00:00:00 2001 From: Septs Date: Tue, 3 Mar 2020 19:10:30 +0800 Subject: [PATCH 09/48] feat: enable format on save --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5fc89a8723b1..2711d07db127 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "typescript.tsdk": "node_modules/typescript/lib", + "editor.formatOnSave": true, "i18n-ally.sourceLanguage": "en", "i18n-ally.displayLanguage": "zh", "i18n-ally.localesPaths": "src/_locales", From b4126302a7b723c9e2c379a463deb2db12c6b86b Mon Sep 17 00:00:00 2001 From: Septs Date: Tue, 3 Mar 2020 20:51:17 +0800 Subject: [PATCH 10/48] fix: windows compatibility --- scripts/ci-build.ts | 4 ++-- scripts/postinstall.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/ci-build.ts b/scripts/ci-build.ts index a504ed5eb935..d73467aa5f57 100644 --- a/scripts/ci-build.ts +++ b/scripts/ci-build.ts @@ -6,8 +6,8 @@ const cwd = path.join(__dirname, '..') const BUILD_PATH = path.join(cwd, 'build') const exec = (command: string, args?: ReadonlyArray, options?: ExecFileSyncOptions) => { - options = { cwd, stdio: [process.stdin, process.stdout, process.stderr], ...options } - execFileSync(command, args, options) + options = { cwd, stdio: [process.stdin, process.stdout, process.stderr], shell: true, ...options } + return execFileSync(command, args, options) } function buildTypes(name: string): string[] { diff --git a/scripts/postinstall.ts b/scripts/postinstall.ts index 1abc8776aff7..2ae180b0706a 100644 --- a/scripts/postinstall.ts +++ b/scripts/postinstall.ts @@ -3,7 +3,7 @@ import path from 'path' const stdio = [process.stdin, process.stdout, process.stderr] const cwd = path.join(__dirname, '..', 'node_modules', '@holoflows', 'kit') -const yarn = (...args: string[]) => execFileSync('yarn', args, { cwd, stdio }) +const yarn = (...args: string[]) => execFileSync('yarn', args, { cwd, stdio, shell: true }) yarn('install') yarn('build:tsc') From f168c47466f16f4f3329ce02f524e5084fff062f Mon Sep 17 00:00:00 2001 From: Septs Date: Tue, 3 Mar 2020 21:19:28 +0800 Subject: [PATCH 11/48] fix: lint-staged in eslint --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c742386df7a2..21cb5da0d26d 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ }, "lint-staged": { "*.{ts,tsx,js,jsx}": [ - "npm run lint" + "eslint --ext .ts,.tsx --fix" ] }, "dependencies": { From 406be318957973834c7d756abee175ce0728ac90 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Tue, 3 Mar 2020 16:37:51 +0800 Subject: [PATCH 12/48] test: add test for post db --- src/database/__tests__/post.ts | 100 +++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/database/__tests__/post.ts diff --git a/src/database/__tests__/post.ts b/src/database/__tests__/post.ts new file mode 100644 index 000000000000..c678bac34f25 --- /dev/null +++ b/src/database/__tests__/post.ts @@ -0,0 +1,100 @@ +import uuid from 'uuid/v4' +import { + PostRecord, + createPostDB, + queryPostDB, + updatePostDB, + createOrUpdatePostDB, + queryPostsDB, + deletePostCryptoKeyDB, + recipientsToNext, + recipientsFromNext, +} from '../post' +import { ProfileIdentifier, PostIVIdentifier, GroupIdentifier } from '../type' +import { generate_AES_GCM_256_Key } from '../../utils/crypto.subtle' + +async function createPostRecord( + postBy: ProfileIdentifier = new ProfileIdentifier(uuid(), uuid()), + identifier: PostIVIdentifier = new PostIVIdentifier(uuid(), uuid()), +) { + return { + postBy, + identifier, + postCryptoKey: await generate_AES_GCM_256_Key(), + recipients: {}, + recipientGroups: [], + foundAt: new Date(), + } as PostRecord +} + +test('createPostDB & queryPostDB', async () => { + const postRecord = await createPostRecord() + await createPostDB(postRecord) + expect(await queryPostDB(postRecord.identifier)).toEqual(postRecord) +}) + +test('updatePostDB', async () => { + const postRecord = await createPostRecord() + const postRecordNew = await createPostRecord() + postRecordNew.identifier = postRecord.identifier + await createPostDB(postRecord) + await updatePostDB(postRecordNew, 'append') + expect(await queryPostDB(postRecord.identifier)).toEqual(postRecordNew) +}) + +test('createOrUpdatePostDB', async () => { + const postRecord = await createPostRecord() + const postRecordNew = await createPostRecord() + postRecordNew.identifier = postRecord.identifier + + expect(await queryPostDB(postRecord.identifier)).toBe(null) + + await createOrUpdatePostDB(postRecord, 'append') + expect(await queryPostDB(postRecord.identifier)).toEqual(postRecord) + + await createOrUpdatePostDB(postRecordNew, 'append') + expect(await queryPostDB(postRecord.identifier)).toEqual(postRecordNew) +}) + +test('queryPostsDB', async () => { + const postRecordA = await createPostRecord() + const postRecordB = await createPostRecord() + const networks = [postRecordA.identifier.network, postRecordB.identifier.network] + const predicate = ({ identifier }: PostRecord) => networks.includes(identifier.network) + + await createPostDB(postRecordA) + await createPostDB(postRecordB) + expect(await queryPostsDB(postRecordA.identifier.network)).toEqual([postRecordA]) + expect((await queryPostsDB(predicate)).every(predicate)).toBeTruthy() +}) + +test('deletePostCryptoKeyDB', async () => { + const postRecord = await createPostRecord() + await createPostDB(postRecord) + expect(await queryPostDB(postRecord.identifier)).toEqual(postRecord) + + await deletePostCryptoKeyDB(postRecord.identifier) + expect(await queryPostDB(postRecord.identifier)).toEqual(null) +}) + +test('recipientsToNext & recipientsFromNext', async () => { + const profileIdentifierA = new ProfileIdentifier(uuid(), uuid()) + const profileIdentifierB = new ProfileIdentifier(uuid(), uuid()) + const groupIdentifier = new GroupIdentifier(uuid(), uuid(), uuid()) + const recipients = { + [profileIdentifierA.toText()]: { + reason: [{ type: 'direct', at: new Date() }], + }, + [profileIdentifierB.toText()]: { + reason: [{ type: 'group', group: groupIdentifier, at: new Date() }], + }, + } as PostRecord['recipients'] + + const next = recipientsToNext(recipients) + expect(next.get(profileIdentifierA)?.reason).toEqual(new Set(recipients[profileIdentifierA.toText()].reason)) + expect(next.get(profileIdentifierB)?.reason).toEqual(new Set(recipients[profileIdentifierB.toText()].reason)) + + const previous = recipientsFromNext(next) + expect(new Set(previous[profileIdentifierA.toText()].reason)).toEqual(next.get(profileIdentifierA)?.reason) + expect(new Set(previous[profileIdentifierB.toText()].reason)).toEqual(next.get(profileIdentifierB)?.reason) +}) From 1ddca6a99f4b428c832dfc138ace2e363f459dad Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2020 11:11:32 +0000 Subject: [PATCH 13/48] chore(deps-dev): bump webpack from 4.41.6 to 4.42.0 Bumps [webpack](https://github.com/webpack/webpack) from 4.41.6 to 4.42.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v4.41.6...v4.42.0) Signed-off-by: dependabot-preview[bot] --- package.json | 2 +- yarn.lock | 34 ++++++---------------------------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 21cb5da0d26d..3425494ad691 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,7 @@ "typechain-target-web3-v1": "^1.0.4", "typescript": "^3.7.5", "web-ext": "^4.1.0", - "webpack": "^4.41.6", + "webpack": "^4.42.0", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.10.3", "webpack-extension-manifest-plugin": "^0.5.0", diff --git a/yarn.lock b/yarn.lock index e0deb7856697..b570d241bc36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16752,15 +16752,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.5.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.1.tgz#eb78f0b945c7bcfa2082b3565e8db3548011dc4f" - integrity sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg== - dependencies: - ajv "^6.10.2" - ajv-keywords "^3.4.1" - -schema-utils@^2.6.1, schema-utils@^2.6.4: +schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.5.0, schema-utils@^2.6.4: version "2.6.4" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.4.tgz#a27efbf6e4e78689d91872ee3ccfa57d7bdd0f53" integrity sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ== @@ -18153,21 +18145,7 @@ terser-webpack-plugin@^1.4.3: webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser-webpack-plugin@^2.1.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.2.tgz#6d3d1b0590c8f729bfbaeb7fb2528b8b62db4c74" - integrity sha512-SmvB/6gtEPv+CJ88MH5zDOsZdKXPS/Uzv2//e90+wM1IHFUhsguPKEILgzqrM1nQ4acRXN/SV4Obr55SXC+0oA== - dependencies: - cacache "^13.0.1" - find-cache-dir "^3.2.0" - jest-worker "^24.9.0" - schema-utils "^2.6.1" - serialize-javascript "^2.1.2" - source-map "^0.6.1" - terser "^4.4.3" - webpack-sources "^1.4.3" - -terser-webpack-plugin@^2.3.4: +terser-webpack-plugin@^2.1.2, terser-webpack-plugin@^2.3.4: version "2.3.4" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.4.tgz#ac045703bd8da0936ce910d8fb6350d0e1dee5fe" integrity sha512-Nv96Nws2R2nrFOpbzF6IxRDpIkkIfmhvOws+IqMvYdFLO7o6wAILWFKONFgaYy8+T4LVz77DQW0f7wOeDEAjrg== @@ -20115,10 +20093,10 @@ webpack-virtual-modules@^0.2.0: dependencies: inquirer "^7.0.0" -webpack@^4.33.0, webpack@^4.38.0, webpack@^4.41.6: - version "4.41.6" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.6.tgz#12f2f804bf6542ef166755050d4afbc8f66ba7e1" - integrity sha512-yxXfV0Zv9WMGRD+QexkZzmGIh54bsvEs+9aRWxnN8erLWEOehAKUTeNBoUbA6HPEZPlRo7KDi2ZcNveoZgK9MA== +webpack@^4.33.0, webpack@^4.38.0, webpack@^4.42.0: + version "4.42.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.42.0.tgz#b901635dd6179391d90740a63c93f76f39883eb8" + integrity sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w== dependencies: "@webassemblyjs/ast" "1.8.5" "@webassemblyjs/helper-module-context" "1.8.5" From 1317967abaec2b388d69b44fb6b167a21d0dbea9 Mon Sep 17 00:00:00 2001 From: Septs Date: Tue, 3 Mar 2020 21:53:00 +0800 Subject: [PATCH 14/48] Revert "fix: lint-staged in eslint" This reverts commit f168c47466f16f4f3329ce02f524e5084fff062f. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3425494ad691..c285c2872863 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ }, "lint-staged": { "*.{ts,tsx,js,jsx}": [ - "eslint --ext .ts,.tsx --fix" + "npm run lint" ] }, "dependencies": { From 66f64ff89890012f9e427f2fa0e7b6c60028120b Mon Sep 17 00:00:00 2001 From: Septs Date: Tue, 3 Mar 2020 21:53:00 +0800 Subject: [PATCH 15/48] Revert "fix: windows compatibility" This reverts commit b4126302a7b723c9e2c379a463deb2db12c6b86b. --- scripts/ci-build.ts | 4 ++-- scripts/postinstall.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/ci-build.ts b/scripts/ci-build.ts index d73467aa5f57..a504ed5eb935 100644 --- a/scripts/ci-build.ts +++ b/scripts/ci-build.ts @@ -6,8 +6,8 @@ const cwd = path.join(__dirname, '..') const BUILD_PATH = path.join(cwd, 'build') const exec = (command: string, args?: ReadonlyArray, options?: ExecFileSyncOptions) => { - options = { cwd, stdio: [process.stdin, process.stdout, process.stderr], shell: true, ...options } - return execFileSync(command, args, options) + options = { cwd, stdio: [process.stdin, process.stdout, process.stderr], ...options } + execFileSync(command, args, options) } function buildTypes(name: string): string[] { diff --git a/scripts/postinstall.ts b/scripts/postinstall.ts index 2ae180b0706a..1abc8776aff7 100644 --- a/scripts/postinstall.ts +++ b/scripts/postinstall.ts @@ -3,7 +3,7 @@ import path from 'path' const stdio = [process.stdin, process.stdout, process.stderr] const cwd = path.join(__dirname, '..', 'node_modules', '@holoflows', 'kit') -const yarn = (...args: string[]) => execFileSync('yarn', args, { cwd, stdio, shell: true }) +const yarn = (...args: string[]) => execFileSync('yarn', args, { cwd, stdio }) yarn('install') yarn('build:tsc') From 9a7affd0e664d199480477c88b2b92ebcae87f5a Mon Sep 17 00:00:00 2001 From: Septs Date: Tue, 3 Mar 2020 21:53:00 +0800 Subject: [PATCH 16/48] Revert "feat: enable format on save" This reverts commit bfc912cc42c381a1d16a38614476253eb867d874. --- .vscode/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2711d07db127..5fc89a8723b1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,5 @@ { "typescript.tsdk": "node_modules/typescript/lib", - "editor.formatOnSave": true, "i18n-ally.sourceLanguage": "en", "i18n-ally.displayLanguage": "zh", "i18n-ally.localesPaths": "src/_locales", From ace2006ea535627701c18f9b2c2ac9b08cfb5c4c Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Wed, 4 Mar 2020 15:40:41 +0800 Subject: [PATCH 17/48] fix: selected count consistency close #831 --- .../shared/SelectRecipients/SelectRecipients.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/shared/SelectRecipients/SelectRecipients.tsx b/src/components/shared/SelectRecipients/SelectRecipients.tsx index d3fa44861a0c..ca8a6fecf37a 100644 --- a/src/components/shared/SelectRecipients/SelectRecipients.tsx +++ b/src/components/shared/SelectRecipients/SelectRecipients.tsx @@ -46,9 +46,9 @@ export function SelectRecipientsUI( x => isProfile(x) && !x.identifier.equals(currentIdentity?.identifier) && x.linkedPersona?.fingerprint, ) as Profile[] const [open, setOpen] = useState(false) + const selectedProfiles = selected.filter(x => isProfile(x)) as Profile[] const selectedGroups = selected.filter(x => isGroup(x)) as Group[] - - const groupMembers = React.useMemo( + const selectedGroupMembers = React.useMemo( () => selectedGroups.flatMap(group => group.members).map(identifier => identifier.toText()), [selectedGroups], ) @@ -75,7 +75,10 @@ export function SelectRecipientsUI( x.identifier.toText()), + ]).size, }), avatar: , disabled: props.disabled || profileItems.length === 0, @@ -88,7 +91,7 @@ export function SelectRecipientsUI( open={open} items={profileItems} selected={profileItems.filter(x => selected.includes(x))} - disabledItems={profileItems.filter(x => groupMembers.includes(x.identifier.toText()))} + disabledItems={profileItems.filter(x => selectedGroupMembers.includes(x.identifier.toText()))} disabled={false} submitDisabled={false} onSubmit={() => setOpen(false)} From 45aaeeaa430b59ff91a8b5da7ff3dbffe92aedaa Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Wed, 4 Mar 2020 14:58:28 +0800 Subject: [PATCH 18/48] test: add test for avatar --- src/database/__tests__/avatar-db.ts | 9 --- src/database/__tests__/avatar.ts | 85 +++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 9 deletions(-) delete mode 100644 src/database/__tests__/avatar-db.ts create mode 100644 src/database/__tests__/avatar.ts diff --git a/src/database/__tests__/avatar-db.ts b/src/database/__tests__/avatar-db.ts deleted file mode 100644 index 35bd66a4326c..000000000000 --- a/src/database/__tests__/avatar-db.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as a from '../avatar' -import { ProfileIdentifier } from '../type' - -const testID = new ProfileIdentifier('localhost', 'test') -test('Store & Query avatar', async () => { - await a.storeAvatarDB(testID, new ArrayBuffer(20)) - const result = await a.queryAvatarDB(testID) - expect(result?.byteLength).toBe(20) -}) diff --git a/src/database/__tests__/avatar.ts b/src/database/__tests__/avatar.ts new file mode 100644 index 000000000000..e2739f3b1d3a --- /dev/null +++ b/src/database/__tests__/avatar.ts @@ -0,0 +1,85 @@ +import uuid from 'uuid/v4' +import { + storeAvatarDB, + queryAvatarDB, + updateAvatarMetaDB, + queryAvatarOutdatedDB, + isAvatarOutdatedDB, + deleteAvatarsDB, +} from '../avatar' +import { ProfileIdentifier, GroupIdentifier } from '../type' + +function createBeforeDate(beforeDays: number) { + return new Date(Date.now() - 1000 * 60 * 60 * 24 * beforeDays) +} + +function createProfileIdentifier() { + return new ProfileIdentifier(uuid(), uuid()) +} + +function createGroupIdentifier() { + return new GroupIdentifier(uuid(), uuid(), uuid()) +} + +function createArrayBuffer(length: number) { + return new Uint8Array(new Array(length).fill(0).map(() => Math.round(Math.random() * 256))).buffer +} + +test('storeAvatarDB & queryAvatarDB', async () => { + const ab = createArrayBuffer(20) + const profileIdentifier = createProfileIdentifier() + const groupIdentifier = createGroupIdentifier() + + await storeAvatarDB(profileIdentifier, ab) + expect(await queryAvatarDB(profileIdentifier)).toEqual(ab) + + await storeAvatarDB(groupIdentifier, ab) + expect(await queryAvatarDB(groupIdentifier)).toEqual(ab) +}) + +test('updateAvatarMetaDB & queryAvatarOutdatedDB', async () => { + const ab = createArrayBuffer(20) + const profileIdentifierA = createProfileIdentifier() + const profileIdentifierB = createProfileIdentifier() + await storeAvatarDB(profileIdentifierA, ab) + await storeAvatarDB(profileIdentifierB, ab) + await updateAvatarMetaDB(profileIdentifierA, { + lastAccessTime: createBeforeDate(15), + }) + await updateAvatarMetaDB(profileIdentifierB, { + lastUpdateTime: createBeforeDate(8), + }) + expect(await queryAvatarOutdatedDB('lastAccessTime')).toEqual([profileIdentifierA]) + expect(await queryAvatarOutdatedDB('lastUpdateTime')).toEqual([profileIdentifierB]) +}) + +test('updateAvatarMetaDB & isAvatarOutdatedDB', async () => { + const ab = createArrayBuffer(20) + const profileIdentifier = createProfileIdentifier() + await storeAvatarDB(profileIdentifier, ab) + expect(await isAvatarOutdatedDB(profileIdentifier, 'lastAccessTime')).toBeFalsy() + expect(await isAvatarOutdatedDB(profileIdentifier, 'lastUpdateTime')).toBeFalsy() + + await updateAvatarMetaDB(profileIdentifier, { + lastAccessTime: createBeforeDate(31), + }) + expect(await isAvatarOutdatedDB(profileIdentifier, 'lastAccessTime')).toBeTruthy() + + await updateAvatarMetaDB(profileIdentifier, { + lastUpdateTime: createBeforeDate(8), + }) + expect(await isAvatarOutdatedDB(profileIdentifier, 'lastUpdateTime')).toBeTruthy() +}) + +test('deleteAvatarsDB', async () => { + const ab = createArrayBuffer(20) + const profileIdentifier = createProfileIdentifier() + + expect(await queryAvatarDB(profileIdentifier)).toBe(null) + + await storeAvatarDB(profileIdentifier, ab) + expect(await queryAvatarDB(profileIdentifier)).toEqual(ab) + + await deleteAvatarsDB([profileIdentifier]) + expect(await queryAvatarDB(profileIdentifier)).toEqual(null) +}) From 4b4e5f58ec8c8a0a7649fe1df8cf1eb5d7e50944 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Wed, 4 Mar 2020 11:38:18 +0800 Subject: [PATCH 19/48] test: add test for group --- src/database/__tests__/group.ts | 86 +++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/database/__tests__/group.ts diff --git a/src/database/__tests__/group.ts b/src/database/__tests__/group.ts new file mode 100644 index 000000000000..5dfa3c876f9c --- /dev/null +++ b/src/database/__tests__/group.ts @@ -0,0 +1,86 @@ +import uuid from 'uuid/v4' +import { + GroupRecord, + createUserGroupDatabase, + queryUserGroupDatabase, + queryUserGroupsDatabase, + createOrUpdateUserGroupDatabase, + deleteUserGroupDatabase, + updateUserGroupDatabase, +} from '../group' +import { GroupIdentifier } from '../type' + +function createGroupRecord() { + const identifier = new GroupIdentifier(uuid(), uuid(), uuid()) + return { + groupName: uuid(), + identifier, + members: [], + banned: undefined, + } as GroupRecord +} + +function groupRecordToDB(groupRecord: GroupRecord) { + return { + ...groupRecord, + network: groupRecord.identifier.network, + } +} + +test('createUserGroupDatabase & queryUserGroupDatabase', async () => { + const groupRecord = createGroupRecord() + await createUserGroupDatabase(groupRecord.identifier, groupRecord.groupName) + expect(await queryUserGroupDatabase(groupRecord.identifier)).toEqual(groupRecordToDB(groupRecord)) +}) + +test('createOrUpdateUserGroupDatabase', async () => { + const groupRecord = createGroupRecord() + const groupRecordNew = createGroupRecord() + groupRecordNew.identifier = groupRecord.identifier + + expect(await queryUserGroupDatabase(groupRecord.identifier)).toBe(null) + + await createOrUpdateUserGroupDatabase(groupRecord, 'replace') + expect(await queryUserGroupDatabase(groupRecord.identifier)).toEqual(groupRecordToDB(groupRecord)) + + await createOrUpdateUserGroupDatabase(groupRecordNew, 'replace') + expect(await queryUserGroupDatabase(groupRecord.identifier)).toEqual(groupRecordToDB(groupRecordNew)) +}) + +test('deleteUserGroupDatabase', async () => { + const groupRecord = createGroupRecord() + expect(await queryUserGroupDatabase(groupRecord.identifier)).toBe(null) + + await createUserGroupDatabase(groupRecord.identifier, groupRecord.groupName) + expect(await queryUserGroupDatabase(groupRecord.identifier)).toEqual(groupRecordToDB(groupRecord)) + + await deleteUserGroupDatabase(groupRecord.identifier) + expect(await queryUserGroupDatabase(groupRecord.identifier)).toBe(null) +}) + +test('updateUserGroupDatabase', async () => { + const groupRecord = createGroupRecord() + const groupRecordNew = createGroupRecord() + groupRecordNew.identifier = groupRecord.identifier + + await createUserGroupDatabase(groupRecord.identifier, groupRecord.groupName) + expect(await queryUserGroupDatabase(groupRecord.identifier)).toEqual(groupRecordToDB(groupRecord)) + + await updateUserGroupDatabase(groupRecordNew, 'replace') + expect(await queryUserGroupDatabase(groupRecord.identifier)).toEqual(groupRecordToDB(groupRecordNew)) +}) + +test('queryUserGroupsDatabase', async () => { + const groupRecordA = createGroupRecord() + const groupRecordB = createGroupRecord() + const networks = [groupRecordA.identifier.network, groupRecordB.identifier.network] + await createUserGroupDatabase(groupRecordA.identifier, groupRecordA.groupName) + await createUserGroupDatabase(groupRecordB.identifier, groupRecordB.groupName) + expect(await queryUserGroupsDatabase({ network: groupRecordA.identifier.network })).toEqual([ + groupRecordToDB(groupRecordA), + ]) + expect(await queryUserGroupsDatabase({ network: groupRecordB.identifier.network })).toEqual([ + groupRecordToDB(groupRecordB), + ]) + expect(await queryUserGroupsDatabase(key => networks.includes(key.network))).toBeTruthy() +}) From e1e815371d5fd23e519754823f2f5c1f0a4244f6 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Wed, 4 Mar 2020 11:49:32 +0800 Subject: [PATCH 20/48] test: longer delay --- src/utils/__tests__/hooks/useQRCodeScan.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/__tests__/hooks/useQRCodeScan.tsx b/src/utils/__tests__/hooks/useQRCodeScan.tsx index 7de8833d7ea0..827bdd0f14cf 100644 --- a/src/utils/__tests__/hooks/useQRCodeScan.tsx +++ b/src/utils/__tests__/hooks/useQRCodeScan.tsx @@ -71,7 +71,7 @@ test('scan succeeded', async () => { await hook.waitForNextUpdate() expect(onResultSpy.calls.count()).toBe(0) - await sleep(200) + await sleep(1000) expect(onResultSpy.calls.count() > 0).toBeTruthy() }) @@ -89,6 +89,6 @@ test('scan failed', async () => { await hook.waitForNextUpdate() expect(onErrorSpy.calls.count()).toBe(0) - await sleep(1200) + await sleep(2000) expect(onErrorSpy.calls.count() > 0).toBeTruthy() }) From f1de7e317d64c143e7ad4fea87928115ab539342 Mon Sep 17 00:00:00 2001 From: Septs Date: Wed, 4 Mar 2020 10:16:09 +0800 Subject: [PATCH 21/48] fix: windows compatibility --- scripts/ci-build.ts | 9 +++++---- scripts/postinstall.ts | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/scripts/ci-build.ts b/scripts/ci-build.ts index a504ed5eb935..0998042170ec 100644 --- a/scripts/ci-build.ts +++ b/scripts/ci-build.ts @@ -1,14 +1,15 @@ import { execFileSync, ExecFileSyncOptions } from 'child_process' import path from 'path' +import os from 'os' import git from '@nice-labs/git-rev' const cwd = path.join(__dirname, '..') const BUILD_PATH = path.join(cwd, 'build') +const stdio = [process.stdin, process.stdout, process.stderr] +const shell = os.platform() === 'win32' -const exec = (command: string, args?: ReadonlyArray, options?: ExecFileSyncOptions) => { - options = { cwd, stdio: [process.stdin, process.stdout, process.stderr], ...options } - execFileSync(command, args, options) -} +const exec = (command: string, args?: string[], options?: ExecFileSyncOptions) => + execFileSync(command, args, { cwd, stdio, shell, ...options }) function buildTypes(name: string): string[] { if (/full/.test(name) || name === 'master') { diff --git a/scripts/postinstall.ts b/scripts/postinstall.ts index 1abc8776aff7..002d848209d0 100644 --- a/scripts/postinstall.ts +++ b/scripts/postinstall.ts @@ -1,9 +1,11 @@ import { execFileSync } from 'child_process' import path from 'path' +import os from 'os' -const stdio = [process.stdin, process.stdout, process.stderr] const cwd = path.join(__dirname, '..', 'node_modules', '@holoflows', 'kit') -const yarn = (...args: string[]) => execFileSync('yarn', args, { cwd, stdio }) +const stdio = [process.stdin, process.stdout, process.stderr] +const shell = os.platform() === 'win32' +const yarn = (...args: string[]) => execFileSync('yarn', args, { cwd, stdio, shell }) yarn('install') yarn('build:tsc') From 762be905cdf051cfd6a85a7b95ce324d35d5e4e8 Mon Sep 17 00:00:00 2001 From: Septs Date: Wed, 4 Mar 2020 10:16:29 +0800 Subject: [PATCH 22/48] fix: lint-staged in eslint --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c285c2872863..3425494ad691 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ }, "lint-staged": { "*.{ts,tsx,js,jsx}": [ - "npm run lint" + "eslint --ext .ts,.tsx --fix" ] }, "dependencies": { From 1f584a460ec9f611e6c1195148356a6fb61b35ce Mon Sep 17 00:00:00 2001 From: Septs Date: Wed, 4 Mar 2020 10:16:39 +0800 Subject: [PATCH 23/48] feat: enable format on save --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5fc89a8723b1..2711d07db127 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "typescript.tsdk": "node_modules/typescript/lib", + "editor.formatOnSave": true, "i18n-ally.sourceLanguage": "en", "i18n-ally.displayLanguage": "zh", "i18n-ally.localesPaths": "src/_locales", From 686022fda8a7d33801021187be9d4e4a9c9ecad6 Mon Sep 17 00:00:00 2001 From: Septs Date: Wed, 4 Mar 2020 11:56:16 +0800 Subject: [PATCH 24/48] chore: use json format replace --- .commitlintrc.json | 3 +++ commitlint.config.js | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 .commitlintrc.json delete mode 100644 commitlint.config.js diff --git a/.commitlintrc.json b/.commitlintrc.json new file mode 100644 index 000000000000..f4fbb7ddefac --- /dev/null +++ b/.commitlintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["@commitlint/config-conventional"] +} diff --git a/commitlint.config.js b/commitlint.config.js deleted file mode 100644 index 3e16e7f1b100..000000000000 --- a/commitlint.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: ['@commitlint/config-conventional'], -} From 94eba374b088a746c25c758ed450097382d7ee9c Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Thu, 5 Mar 2020 10:32:29 +0800 Subject: [PATCH 25/48] fix: webpack env is undefined --- scripts/jest-setup.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/jest-setup.js b/scripts/jest-setup.js index afd4f230aa9e..4b2f92d163ce 100644 --- a/scripts/jest-setup.js +++ b/scripts/jest-setup.js @@ -55,3 +55,8 @@ globalThis.webkit.messageHandlers = globalThis.webkit.messageHandlers || {} globalThis.webkit.messageHandlers.maskbookjsonrpc = { postMessage(data) {}, } + +// webpack env +globalThis.webpackEnv = { + target: 'Chromium', +} From bd59cc1f83f2e5c82fc41b42f97509bbdf39f96f Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Thu, 5 Mar 2020 10:50:22 +0800 Subject: [PATCH 26/48] test: run ci test cases in single tread mode --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3425494ad691..2ab1291efff0 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "start:ios": "webpack-dev-server --wk-webview --mode development", "storybook": "start-storybook -p 9009 -s public --quiet", "test": "jest --watch", - "test:ci": "jest --ci --collectCoverage=true --reporters=default --reporters=jest-junit", + "test:ci": "jest --ci --collectCoverage=true --reporters=default --reporters=jest-junit -w 1", "uninstall": "ts-node scripts/postinstall.ts" }, "husky": { From d6aaa3c31abb0fb92f440be3b58f48b116db058e Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Thu, 5 Mar 2020 11:18:47 +0800 Subject: [PATCH 27/48] refactor: compress warning --- src/crypto/__tests__/crypto-alpha-38-test.ts | 4 ++++ src/database/__tests__/IdentifierMap.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/crypto/__tests__/crypto-alpha-38-test.ts b/src/crypto/__tests__/crypto-alpha-38-test.ts index 86e4da723d40..ddaf829f5fd8 100644 --- a/src/crypto/__tests__/crypto-alpha-38-test.ts +++ b/src/crypto/__tests__/crypto-alpha-38-test.ts @@ -26,6 +26,10 @@ async function aesFromSeed(seed: string) { return derive_AES_GCM_256_Key_From_PBKDF2(await import_PBKDF2_Key(encodeText(seed)), encodeText('iv')) } +beforeAll(() => { + spyOn(globalThis.console, 'warn') +}) + // Test for: c.typedMessageParse && c.typedMessageStringify test('Crypto alpha v38 Typed Message', () => { diff --git a/src/database/__tests__/IdentifierMap.ts b/src/database/__tests__/IdentifierMap.ts index b81b485cc6c3..c45958827702 100644 --- a/src/database/__tests__/IdentifierMap.ts +++ b/src/database/__tests__/IdentifierMap.ts @@ -4,6 +4,11 @@ import { ProfileIdentifier, Identifier, PostIVIdentifier, PostIdentifier } from const a = new ProfileIdentifier('localhost', 'id') const aa = new ProfileIdentifier('localhost', 'id') const b = new ProfileIdentifier('localhost', 'id2') + +beforeAll(() => { + spyOn(globalThis.console, 'warn') +}) + test('Map', () => { const x = new IdentifierMap(new Map()) x.set(a, 1) From 49bd889f61dfbb324300940527247e0e96840476 Mon Sep 17 00:00:00 2001 From: SunriseFox Date: Wed, 4 Mar 2020 10:11:40 +0800 Subject: [PATCH 28/48] chore: minimizable stepper --- .../ImmersiveSetup/DraggablePaper.tsx | 6 +- .../ImmersiveSetup/SetupStepper.tsx | 68 +++++++++++++++---- src/stories/Immersive-Setup.tsx | 29 ++++---- 3 files changed, 72 insertions(+), 31 deletions(-) diff --git a/src/components/InjectedComponents/ImmersiveSetup/DraggablePaper.tsx b/src/components/InjectedComponents/ImmersiveSetup/DraggablePaper.tsx index 5f32daba0cb7..ef16d0122650 100644 --- a/src/components/InjectedComponents/ImmersiveSetup/DraggablePaper.tsx +++ b/src/components/InjectedComponents/ImmersiveSetup/DraggablePaper.tsx @@ -1,5 +1,4 @@ import React from 'react' -import Paper, { PaperProps } from '@material-ui/core/Paper' import Draggable from 'react-draggable' import { makeStyles, Theme } from '@material-ui/core' @@ -19,15 +18,14 @@ const useStyle = makeStyles((theme: Theme) => ({ top: '2em', right: '2em', pointerEvents: 'initial', - outline: theme.palette.type === 'dark' ? '1px solid rgba(255, 255, 255, 0.5)' : undefined, }, })) -export function DraggablePaper(props: PaperProps) { +export function DraggablePaper(props: React.HTMLAttributes) { const classes = useStyle() return (
- +
) diff --git a/src/components/InjectedComponents/ImmersiveSetup/SetupStepper.tsx b/src/components/InjectedComponents/ImmersiveSetup/SetupStepper.tsx index 28c8e81460bb..ea96a7d7e2b2 100644 --- a/src/components/InjectedComponents/ImmersiveSetup/SetupStepper.tsx +++ b/src/components/InjectedComponents/ImmersiveSetup/SetupStepper.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState, useCallback } from 'react' import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' import Stepper from '@material-ui/core/Stepper' import Step from '@material-ui/core/Step' @@ -6,7 +6,7 @@ import StepLabel from '@material-ui/core/StepLabel' import StepContent from '@material-ui/core/StepContent' import Button from '@material-ui/core/Button' import Typography from '@material-ui/core/Typography' -import { TextField, Link, AppBar, Toolbar, IconButton } from '@material-ui/core' +import { TextField, AppBar, Toolbar, IconButton, Paper } from '@material-ui/core' import { ActionButtonPromise } from '../../../extension/options-page/DashboardComponents/ActionButton' import { sleep } from '@holoflows/kit/es/util/sleep' import { getActivatedUI } from '../../../social-network/ui' @@ -14,10 +14,13 @@ import { useValueRef } from '../../../utils/hooks/useValueRef' import { ProfileIdentifier, PersonaIdentifier } from '../../../database/type' import { useCapturedInput } from '../../../utils/hooks/useCapturedEvents' import CloseIcon from '@material-ui/icons/Close' +import RemoveIcon from '@material-ui/icons/Remove' +import AddIcon from '@material-ui/icons/Add' import { currentImmersiveSetupStatus, ImmersiveSetupCrossContextStatus } from '../../shared-settings/settings' import Services from '../../../extension/service' import { useI18N } from '../../../utils/i18n-next-ui' import { selectElementContents } from '../../../utils/utils' +import classNames from 'classnames' const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -46,6 +49,16 @@ const useStyles = makeStyles((theme: Theme) => border: `1px solid ${theme.palette.error.main}`, }, header: { cursor: 'move' }, + body: { + transition: 'transform 0.1s ease', + transformOrigin: 'top center', + }, + minimized: { + transform: 'scaleY(0)', + }, + minimizeend: { + height: 0, + }, }), ) @@ -72,6 +85,16 @@ export function ImmersiveSetupStepperUI(props: ImmersiveSetupStepperUIProps) { const steps = getSteps() const activeStep = props.currentStep const [, inputRef] = useCapturedInput(props.onUsernameChange) + const [minimized, _setMinimized] = useState( + () => + !!globalThis.location.href.match(/\/login(?:\.php|\/)/) || + getActivatedUI().lastRecognizedIdentity.value.identifier.isUnknown, + ) + const [minimizeend, setMinimizeend] = useState(() => minimized) + const setMinimized = useCallback((e: boolean) => { + setMinimizeend(false) + _setMinimized(e) + }, []) const ERROR_TEXT = t('immersive_setup_no_bio_got') @@ -93,20 +116,37 @@ export function ImmersiveSetupStepperUI(props: ImmersiveSetupStepperUIProps) { - {t('immersive_setup_title')} + + {t(minimized ? 'banner_get_started' : 'immersive_setup_title')} + + setMinimized(!minimized)}> + {minimized ? : } + - - {steps.map((label, index) => ( - - {label} - - {getStepContent(index)} - {actions} - - - ))} - + setMinimizeend(minimized)}> + + {steps.map((label, index) => ( + + {label} + + {getStepContent(index)} + {actions} + + + ))} + + {/* {activeStep === steps.length && {getBioStatus()}} */} ) diff --git a/src/stories/Immersive-Setup.tsx b/src/stories/Immersive-Setup.tsx index 97b57858c336..1dc60bb4eb39 100644 --- a/src/stories/Immersive-Setup.tsx +++ b/src/stories/Immersive-Setup.tsx @@ -8,19 +8,22 @@ import { text } from '@storybook/addon-knobs' import { sleep } from '@holoflows/kit/es/util/sleep' import { action } from '@storybook/addon-actions' import { ECKeyIdentifier } from '../database/type' +import { DraggablePaper } from '../components/InjectedComponents/ImmersiveSetup/DraggablePaper' storiesOf('Immersive Setup', module).add('Stepper', () => ( - { - action('loadProfile')() - await sleep(700) - }} - provePost={text('Prove post', '🎭A81Kg7HVsITcftN/0IBp2q6+IyfZCYHntkVsMTRl741L0🎭')} - onClose={action('close')} - autoPasteProvePost={async () => { - action('autoPasteProvePost')() - await sleep(700) - }} - /> + + { + action('loadProfile')() + await sleep(700) + }} + provePost={text('Prove post', '🎭A81Kg7HVsITcftN/0IBp2q6+IyfZCYHntkVsMTRl741L0🎭')} + onClose={action('close')} + autoPasteProvePost={async () => { + action('autoPasteProvePost')() + await sleep(700) + }} + /> + )) From bb7b3b5c76bed6c8f0875b53df12f2b4baed06e1 Mon Sep 17 00:00:00 2001 From: SunriseFox Date: Wed, 4 Mar 2020 14:01:16 +0800 Subject: [PATCH 29/48] chore: switch to material-ui transition --- .../ImmersiveSetup/SetupStepper.tsx | 49 +++++++------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/src/components/InjectedComponents/ImmersiveSetup/SetupStepper.tsx b/src/components/InjectedComponents/ImmersiveSetup/SetupStepper.tsx index ea96a7d7e2b2..14951ca8e21c 100644 --- a/src/components/InjectedComponents/ImmersiveSetup/SetupStepper.tsx +++ b/src/components/InjectedComponents/ImmersiveSetup/SetupStepper.tsx @@ -6,7 +6,7 @@ import StepLabel from '@material-ui/core/StepLabel' import StepContent from '@material-ui/core/StepContent' import Button from '@material-ui/core/Button' import Typography from '@material-ui/core/Typography' -import { TextField, AppBar, Toolbar, IconButton, Paper } from '@material-ui/core' +import { TextField, AppBar, Toolbar, IconButton, Paper, Collapse } from '@material-ui/core' import { ActionButtonPromise } from '../../../extension/options-page/DashboardComponents/ActionButton' import { sleep } from '@holoflows/kit/es/util/sleep' import { getActivatedUI } from '../../../social-network/ui' @@ -53,12 +53,6 @@ const useStyles = makeStyles((theme: Theme) => transition: 'transform 0.1s ease', transformOrigin: 'top center', }, - minimized: { - transform: 'scaleY(0)', - }, - minimizeend: { - height: 0, - }, }), ) @@ -85,16 +79,11 @@ export function ImmersiveSetupStepperUI(props: ImmersiveSetupStepperUIProps) { const steps = getSteps() const activeStep = props.currentStep const [, inputRef] = useCapturedInput(props.onUsernameChange) - const [minimized, _setMinimized] = useState( + const [minimized, setMinimized] = useState( () => !!globalThis.location.href.match(/\/login(?:\.php|\/)/) || getActivatedUI().lastRecognizedIdentity.value.identifier.isUnknown, ) - const [minimizeend, setMinimizeend] = useState(() => minimized) - const setMinimized = useCallback((e: boolean) => { - setMinimizeend(false) - _setMinimized(e) - }, []) const ERROR_TEXT = t('immersive_setup_no_bio_got') @@ -128,25 +117,21 @@ export function ImmersiveSetupStepperUI(props: ImmersiveSetupStepperUIProps) { - setMinimizeend(minimized)}> - - {steps.map((label, index) => ( - - {label} - - {getStepContent(index)} - {actions} - - - ))} - - + + + + {steps.map((label, index) => ( + + {label} + + {getStepContent(index)} + {actions} + + + ))} + + + {/* {activeStep === steps.length && {getBioStatus()}} */} ) From 1be57cc6ed96c4afaa41aaefcb7147d31504f7b5 Mon Sep 17 00:00:00 2001 From: SunriseFox Date: Wed, 4 Mar 2020 14:02:26 +0800 Subject: [PATCH 30/48] chore: rename draggable paper to draggable div --- .../ImmersiveSetup/{DraggablePaper.tsx => DraggableDiv.tsx} | 2 +- .../defaults/taskStartImmersiveSetupDefault.tsx | 6 +++--- src/stories/Immersive-Setup.tsx | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) rename src/components/InjectedComponents/ImmersiveSetup/{DraggablePaper.tsx => DraggableDiv.tsx} (90%) diff --git a/src/components/InjectedComponents/ImmersiveSetup/DraggablePaper.tsx b/src/components/InjectedComponents/ImmersiveSetup/DraggableDiv.tsx similarity index 90% rename from src/components/InjectedComponents/ImmersiveSetup/DraggablePaper.tsx rename to src/components/InjectedComponents/ImmersiveSetup/DraggableDiv.tsx index ef16d0122650..ef098f58528e 100644 --- a/src/components/InjectedComponents/ImmersiveSetup/DraggablePaper.tsx +++ b/src/components/InjectedComponents/ImmersiveSetup/DraggableDiv.tsx @@ -20,7 +20,7 @@ const useStyle = makeStyles((theme: Theme) => ({ pointerEvents: 'initial', }, })) -export function DraggablePaper(props: React.HTMLAttributes) { +export function DraggableDiv(props: React.HTMLAttributes) { const classes = useStyle() return (
diff --git a/src/social-network/defaults/taskStartImmersiveSetupDefault.tsx b/src/social-network/defaults/taskStartImmersiveSetupDefault.tsx index dd08791c53c4..f93e20645175 100644 --- a/src/social-network/defaults/taskStartImmersiveSetupDefault.tsx +++ b/src/social-network/defaults/taskStartImmersiveSetupDefault.tsx @@ -5,7 +5,7 @@ import { ImmersiveSetupStepper, ImmersiveSetupStepperUIProps, } from '../../components/InjectedComponents/ImmersiveSetup/SetupStepper' -import { DraggablePaper } from '../../components/InjectedComponents/ImmersiveSetup/DraggablePaper' +import { DraggableDiv } from '../../components/InjectedComponents/ImmersiveSetup/DraggableDiv' import Services from '../../extension/service' import { ValueRef } from '@holoflows/kit/es' import { useValueRef } from '../../utils/hooks/useValueRef' @@ -22,13 +22,13 @@ function UI({ >) { const provePost = useValueRef(post) return ( - + - + ) } let mounted = false diff --git a/src/stories/Immersive-Setup.tsx b/src/stories/Immersive-Setup.tsx index 1dc60bb4eb39..589f79374ded 100644 --- a/src/stories/Immersive-Setup.tsx +++ b/src/stories/Immersive-Setup.tsx @@ -8,10 +8,10 @@ import { text } from '@storybook/addon-knobs' import { sleep } from '@holoflows/kit/es/util/sleep' import { action } from '@storybook/addon-actions' import { ECKeyIdentifier } from '../database/type' -import { DraggablePaper } from '../components/InjectedComponents/ImmersiveSetup/DraggablePaper' +import { DraggableDiv } from '../components/InjectedComponents/ImmersiveSetup/DraggableDiv' storiesOf('Immersive Setup', module).add('Stepper', () => ( - + { @@ -25,5 +25,5 @@ storiesOf('Immersive Setup', module).add('Stepper', () => ( await sleep(700) }} /> - + )) From a4f9d6fa866ddb137cd96b56abb689f2ae6660ee Mon Sep 17 00:00:00 2001 From: SunriseFox Date: Thu, 5 Mar 2020 09:38:43 +0800 Subject: [PATCH 31/48] chore: minimized opacity --- .../ImmersiveSetup/SetupStepper.tsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/InjectedComponents/ImmersiveSetup/SetupStepper.tsx b/src/components/InjectedComponents/ImmersiveSetup/SetupStepper.tsx index 14951ca8e21c..efdd97f689e1 100644 --- a/src/components/InjectedComponents/ImmersiveSetup/SetupStepper.tsx +++ b/src/components/InjectedComponents/ImmersiveSetup/SetupStepper.tsx @@ -48,7 +48,10 @@ const useStyles = makeStyles((theme: Theme) => padding: 6, border: `1px solid ${theme.palette.error.main}`, }, - header: { cursor: 'move' }, + header: { cursor: 'move', transition: 'opacity 0.4s ease' }, + minimizedHeader: { + opacity: 0.26, + }, body: { transition: 'transform 0.1s ease', transformOrigin: 'top center', @@ -100,11 +103,16 @@ export function ImmersiveSetupStepperUI(props: ImmersiveSetupStepperUIProps) { ) return (