Lex refactor (#362)

* Remove the hackcheck for upgrades

* Rename the PostEmbeds folder to match the codebase style

* Updates to latest lex refactor

* Update to use new bsky agent

* Update to use api package's richtext library

* Switch to upsertProfile

* Add TextEncoder/TextDecoder polyfill

* Add Intl.Segmenter polyfill

* Update composer to calculate lengths by grapheme

* Fix detox

* Fix login in e2e

* Create account e2e passing

* Implement an e2e mocking framework

* Don't use private methods on mobx models as mobx can't track them

* Add tooling for e2e-specific builds and add e2e media-picker mock

* Add some tests and fix some bugs around profile editing

* Add shell tests

* Add home screen tests

* Add thread screen tests

* Add tests for other user profile screens

* Add search screen tests

* Implement profile imagery change tools and tests

* Update to new embed behaviors

* Add post tests

* Fix to profile-screen test

* Fix session resumption

* Update web composer to new api

* 1.11.0

* Fix pagination cursor parameters

* Add quote posts to notifications

* Fix embed layouts

* Remove youtube inline player and improve tap handling on link cards

* Reset minimal shell mode on all screen loads and feed swipes (close #299)

* Update podfile.lock

* Improve post notfound UI (close #366)

* Bump atproto packages
This commit is contained in:
Paul Frazee 2023-03-31 13:17:26 -05:00 committed by GitHub
parent 19f3a2fa92
commit a3334a01a2
133 changed files with 3103 additions and 2839 deletions

View file

@ -4,14 +4,14 @@ import {
getLikelyType,
} from '../../src/lib/link-meta/link-meta'
import {exampleComHtml} from './__mocks__/exampleComHtml'
import AtpAgent from '@atproto/api'
import {BskyAgent} from '@atproto/api'
import {DEFAULT_SERVICE, RootStoreModel} from '../../src/state'
describe('getLinkMeta', () => {
let rootStore: RootStoreModel
beforeEach(() => {
rootStore = new RootStoreModel(new AtpAgent({service: DEFAULT_SERVICE}))
rootStore = new RootStoreModel(new BskyAgent({service: DEFAULT_SERVICE}))
})
const inputs = [

View file

@ -7,172 +7,10 @@ import {
} from '../../src/lib/strings/url-helpers'
import {pluralize, enforceLen} from '../../src/lib/strings/helpers'
import {ago} from '../../src/lib/strings/time'
import {
extractEntities,
detectLinkables,
} from '../../src/lib/strings/rich-text-detection'
import {detectLinkables} from '../../src/lib/strings/rich-text-detection'
import {makeValidHandle, createFullHandle} from '../../src/lib/strings/handles'
import {cleanError} from '../../src/lib/strings/errors'
describe('extractEntities', () => {
const knownHandles = new Set(['handle.com', 'full123.test-of-chars'])
const inputs = [
'no mention',
'@handle.com middle end',
'start @handle.com end',
'start middle @handle.com',
'@handle.com @handle.com @handle.com',
'@full123.test-of-chars',
'not@right',
'@handle.com!@#$chars',
'@handle.com\n@handle.com',
'parenthetical (@handle.com)',
'start https://middle.com end',
'start https://middle.com/foo/bar end',
'start https://middle.com/foo/bar?baz=bux end',
'start https://middle.com/foo/bar?baz=bux#hash end',
'https://start.com/foo/bar?baz=bux#hash middle end',
'start middle https://end.com/foo/bar?baz=bux#hash',
'https://newline1.com\nhttps://newline2.com',
'start middle.com end',
'start middle.com/foo/bar end',
'start middle.com/foo/bar?baz=bux end',
'start middle.com/foo/bar?baz=bux#hash end',
'start.com/foo/bar?baz=bux#hash middle end',
'start middle end.com/foo/bar?baz=bux#hash',
'newline1.com\nnewline2.com',
'not.. a..url ..here',
'e.g.',
'something-cool.jpg',
'website.com.jpg',
'e.g./foo',
'website.com.jpg/foo',
'Classic article https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/',
'Classic article https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/ ',
'https://foo.com https://bar.com/whatever https://baz.com',
'punctuation https://foo.com, https://bar.com/whatever; https://baz.com.',
'parenthentical (https://foo.com)',
'except for https://foo.com/thing_(cool)',
]
interface Output {
type: string
value: string
noScheme?: boolean
}
const outputs: Output[][] = [
[],
[{type: 'mention', value: 'handle.com'}],
[{type: 'mention', value: 'handle.com'}],
[{type: 'mention', value: 'handle.com'}],
[
{type: 'mention', value: 'handle.com'},
{type: 'mention', value: 'handle.com'},
{type: 'mention', value: 'handle.com'},
],
[
{
type: 'mention',
value: 'full123.test-of-chars',
},
],
[],
[{type: 'mention', value: 'handle.com'}],
[
{type: 'mention', value: 'handle.com'},
{type: 'mention', value: 'handle.com'},
],
[{type: 'mention', value: 'handle.com'}],
[{type: 'link', value: 'https://middle.com'}],
[{type: 'link', value: 'https://middle.com/foo/bar'}],
[{type: 'link', value: 'https://middle.com/foo/bar?baz=bux'}],
[{type: 'link', value: 'https://middle.com/foo/bar?baz=bux#hash'}],
[{type: 'link', value: 'https://start.com/foo/bar?baz=bux#hash'}],
[{type: 'link', value: 'https://end.com/foo/bar?baz=bux#hash'}],
[
{type: 'link', value: 'https://newline1.com'},
{type: 'link', value: 'https://newline2.com'},
],
[{type: 'link', value: 'middle.com', noScheme: true}],
[{type: 'link', value: 'middle.com/foo/bar', noScheme: true}],
[{type: 'link', value: 'middle.com/foo/bar?baz=bux', noScheme: true}],
[{type: 'link', value: 'middle.com/foo/bar?baz=bux#hash', noScheme: true}],
[{type: 'link', value: 'start.com/foo/bar?baz=bux#hash', noScheme: true}],
[{type: 'link', value: 'end.com/foo/bar?baz=bux#hash', noScheme: true}],
[
{type: 'link', value: 'newline1.com', noScheme: true},
{type: 'link', value: 'newline2.com', noScheme: true},
],
[],
[],
[],
[],
[],
[],
[
{
type: 'link',
value:
'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/',
},
],
[
{
type: 'link',
value:
'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/',
},
],
[
{type: 'link', value: 'https://foo.com'},
{type: 'link', value: 'https://bar.com/whatever'},
{type: 'link', value: 'https://baz.com'},
],
[
{type: 'link', value: 'https://foo.com'},
{type: 'link', value: 'https://bar.com/whatever'},
{type: 'link', value: 'https://baz.com'},
],
[{type: 'link', value: 'https://foo.com'}],
[{type: 'link', value: 'https://foo.com/thing_(cool)'}],
]
it('correctly handles a set of text inputs', () => {
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i]
const result = extractEntities(input, knownHandles)
if (!outputs[i].length) {
expect(result).toBeFalsy()
} else if (outputs[i].length && !result) {
expect(result).toBeTruthy()
} else if (result) {
expect(result.length).toBe(outputs[i].length)
for (let j = 0; j < outputs[i].length; j++) {
expect(result[j].type).toEqual(outputs[i][j].type)
if (outputs[i][j].noScheme) {
expect(result[j].value).toEqual(`https://${outputs[i][j].value}`)
} else {
expect(result[j].value).toEqual(outputs[i][j].value)
}
if (outputs[i]?.[j].type === 'mention') {
expect(
input.slice(result[j].index.start, result[j].index.end),
).toBe(`@${result[j].value}`)
} else {
if (!outputs[i]?.[j].noScheme) {
expect(
input.slice(result[j].index.start, result[j].index.end),
).toBe(result[j].value)
} else {
expect(
input.slice(result[j].index.start, result[j].index.end),
).toBe(result[j].value.slice('https://'.length))
}
}
}
}
}
})
})
describe('detectLinkables', () => {
const inputs = [
'no linkable',

View file

@ -1,123 +0,0 @@
import {AppBskyFeedPost} from '@atproto/api'
type Entity = AppBskyFeedPost.Entity
import {RichText} from '../../../src/lib/strings/rich-text'
import {removeExcessNewlines} from '../../../src/lib/strings/rich-text-sanitize'
describe('removeExcessNewlines', () => {
it('removes more than two consecutive new lines', () => {
const input = new RichText(
'test\n\n\n\n\ntest\n\n\n\n\n\n\ntest\n\n\n\n\n\n\ntest\n\n\n\n\n\n\ntest',
)
const output = removeExcessNewlines(input)
expect(output.text).toEqual('test\n\ntest\n\ntest\n\ntest\n\ntest')
})
it('removes more than two consecutive new lines with spaces', () => {
const input = new RichText(
'test\n\n\n\n\ntest\n \n \n \n \n\n\ntest\n\n\n\n\n\n\ntest\n\n\n\n\n \n\ntest',
)
const output = removeExcessNewlines(input)
expect(output.text).toEqual('test\n\ntest\n\ntest\n\ntest\n\ntest')
})
it('returns original string if there are no consecutive new lines', () => {
const input = new RichText('test\n\ntest\n\ntest\n\ntest\n\ntest')
const output = removeExcessNewlines(input)
expect(output.text).toEqual(input.text)
})
it('returns original string if there are no new lines', () => {
const input = new RichText('test test test test test')
const output = removeExcessNewlines(input)
expect(output.text).toEqual(input.text)
})
it('returns empty string if input is empty', () => {
const input = new RichText('')
const output = removeExcessNewlines(input)
expect(output.text).toEqual('')
})
it('works with different types of new line characters', () => {
const input = new RichText(
'test\r\ntest\n\rtest\rtest\n\n\n\ntest\n\r \n \n \n \n\n\ntest',
)
const output = removeExcessNewlines(input)
expect(output.text).toEqual('test\r\ntest\n\rtest\rtest\n\ntest\n\ntest')
})
it('removes more than two consecutive new lines with zero width space', () => {
const input = new RichText(
'test\n\n\n\n\ntest\n\u200B\u200B\n\n\n\ntest\n \u200B\u200B \n\n\n\ntest\n\n\n\n\n\n\ntest',
)
const output = removeExcessNewlines(input)
expect(output.text).toEqual('test\n\ntest\n\ntest\n\ntest\n\ntest')
})
it('removes more than two consecutive new lines with zero width non-joiner', () => {
const input = new RichText(
'test\n\n\n\n\ntest\n\u200C\u200C\n\n\n\ntest\n \u200C\u200C \n\n\n\ntest\n\n\n\n\n\n\ntest',
)
const output = removeExcessNewlines(input)
expect(output.text).toEqual('test\n\ntest\n\ntest\n\ntest\n\ntest')
})
it('removes more than two consecutive new lines with zero width joiner', () => {
const input = new RichText(
'test\n\n\n\n\ntest\n\u200D\u200D\n\n\n\ntest\n \u200D\u200D \n\n\n\ntest\n\n\n\n\n\n\ntest',
)
const output = removeExcessNewlines(input)
expect(output.text).toEqual('test\n\ntest\n\ntest\n\ntest\n\ntest')
})
it('removes more than two consecutive new lines with soft hyphen', () => {
const input = new RichText(
'test\n\n\n\n\ntest\n\u00AD\u00AD\n\n\n\ntest\n \u00AD\u00AD \n\n\n\ntest\n\n\n\n\n\n\ntest',
)
const output = removeExcessNewlines(input)
expect(output.text).toEqual('test\n\ntest\n\ntest\n\ntest\n\ntest')
})
it('removes more than two consecutive new lines with word joiner', () => {
const input = new RichText(
'test\n\n\n\n\ntest\n\u2060\u2060\n\n\n\ntest\n \u2060\u2060 \n\n\n\ntest\n\n\n\n\n\n\ntest',
)
const output = removeExcessNewlines(input)
expect(output.text).toEqual('test\n\ntest\n\ntest\n\ntest\n\ntest')
})
})
describe('removeExcessNewlines w/entities', () => {
it('preserves entities as expected', () => {
const input = new RichText(
'test\n\n\n\n\ntest\n\n\n\n\n\n\ntest\n\n\n\n\n\n\ntest\n\n\n\n\n\n\ntest',
[
{index: {start: 0, end: 13}, type: '', value: ''},
{index: {start: 13, end: 24}, type: '', value: ''},
{index: {start: 9, end: 15}, type: '', value: ''},
{index: {start: 4, end: 9}, type: '', value: ''},
],
)
const output = removeExcessNewlines(input)
expect(entToStr(input.text, input.entities?.[0])).toEqual(
'test\n\n\n\n\ntest',
)
expect(entToStr(input.text, input.entities?.[1])).toEqual(
'\n\n\n\n\n\n\ntest',
)
expect(entToStr(input.text, input.entities?.[2])).toEqual('test\n\n')
expect(entToStr(input.text, input.entities?.[3])).toEqual('\n\n\n\n\n')
expect(output.text).toEqual('test\n\ntest\n\ntest\n\ntest\n\ntest')
expect(entToStr(output.text, output.entities?.[0])).toEqual('test\n\ntest')
expect(entToStr(output.text, output.entities?.[1])).toEqual('test')
expect(entToStr(output.text, output.entities?.[2])).toEqual('test')
expect(output.entities?.[3]).toEqual(undefined)
})
})
function entToStr(str: string, ent?: Entity) {
if (!ent) {
return ''
}
return str.slice(ent.index.start, ent.index.end)
}

View file

@ -1,123 +0,0 @@
import {RichText} from '../../../src/lib/strings/rich-text'
describe('richText.insert', () => {
const input = new RichText('hello world', [
{index: {start: 2, end: 7}, type: '', value: ''},
])
it('correctly adjusts entities (scenario A - before)', () => {
const output = input.clone().insert(0, 'test')
expect(output.text).toEqual('testhello world')
expect(output.entities?.[0].index.start).toEqual(6)
expect(output.entities?.[0].index.end).toEqual(11)
expect(
output.text.slice(
output.entities?.[0].index.start,
output.entities?.[0].index.end,
),
).toEqual('llo w')
})
it('correctly adjusts entities (scenario B - inner)', () => {
const output = input.clone().insert(4, 'test')
expect(output.text).toEqual('helltesto world')
expect(output.entities?.[0].index.start).toEqual(2)
expect(output.entities?.[0].index.end).toEqual(11)
expect(
output.text.slice(
output.entities?.[0].index.start,
output.entities?.[0].index.end,
),
).toEqual('lltesto w')
})
it('correctly adjusts entities (scenario C - after)', () => {
const output = input.clone().insert(8, 'test')
expect(output.text).toEqual('hello wotestrld')
expect(output.entities?.[0].index.start).toEqual(2)
expect(output.entities?.[0].index.end).toEqual(7)
expect(
output.text.slice(
output.entities?.[0].index.start,
output.entities?.[0].index.end,
),
).toEqual('llo w')
})
})
describe('richText.delete', () => {
const input = new RichText('hello world', [
{index: {start: 2, end: 7}, type: '', value: ''},
])
it('correctly adjusts entities (scenario A - entirely outer)', () => {
const output = input.clone().delete(0, 9)
expect(output.text).toEqual('ld')
expect(output.entities?.length).toEqual(0)
})
it('correctly adjusts entities (scenario B - entirely after)', () => {
const output = input.clone().delete(7, 11)
expect(output.text).toEqual('hello w')
expect(output.entities?.[0].index.start).toEqual(2)
expect(output.entities?.[0].index.end).toEqual(7)
expect(
output.text.slice(
output.entities?.[0].index.start,
output.entities?.[0].index.end,
),
).toEqual('llo w')
})
it('correctly adjusts entities (scenario C - partially after)', () => {
const output = input.clone().delete(4, 11)
expect(output.text).toEqual('hell')
expect(output.entities?.[0].index.start).toEqual(2)
expect(output.entities?.[0].index.end).toEqual(4)
expect(
output.text.slice(
output.entities?.[0].index.start,
output.entities?.[0].index.end,
),
).toEqual('ll')
})
it('correctly adjusts entities (scenario D - entirely inner)', () => {
const output = input.clone().delete(3, 5)
expect(output.text).toEqual('hel world')
expect(output.entities?.[0].index.start).toEqual(2)
expect(output.entities?.[0].index.end).toEqual(5)
expect(
output.text.slice(
output.entities?.[0].index.start,
output.entities?.[0].index.end,
),
).toEqual('l w')
})
it('correctly adjusts entities (scenario E - partially before)', () => {
const output = input.clone().delete(1, 5)
expect(output.text).toEqual('h world')
expect(output.entities?.[0].index.start).toEqual(1)
expect(output.entities?.[0].index.end).toEqual(3)
expect(
output.text.slice(
output.entities?.[0].index.start,
output.entities?.[0].index.end,
),
).toEqual(' w')
})
it('correctly adjusts entities (scenario F - entirely before)', () => {
const output = input.clone().delete(0, 2)
expect(output.text).toEqual('llo world')
expect(output.entities?.[0].index.start).toEqual(0)
expect(output.entities?.[0].index.end).toEqual(5)
expect(
output.text.slice(
output.entities?.[0].index.start,
output.entities?.[0].index.end,
),
).toEqual('llo w')
})
})