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:
parent
19f3a2fa92
commit
a3334a01a2
133 changed files with 3103 additions and 2839 deletions
|
@ -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 = [
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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')
|
||||
})
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue