mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-09-21 07:28:30 +03:00
Merge pull request #5102 from urbit/lf/int-fixes
interface: assorted fixes
This commit is contained in:
commit
42b992edbd
11
pkg/arvo/gen/group-view/join.hoon
Normal file
11
pkg/arvo/gen/group-view/join.hoon
Normal file
@ -0,0 +1,11 @@
|
||||
:: group-view|join: Join a group
|
||||
::
|
||||
/- view=group-view
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[him=ship name=term ~] ~]
|
||||
==
|
||||
::
|
||||
:- %group-view-action
|
||||
^- action:view
|
||||
[%join [him name] him]
|
@ -405,26 +405,17 @@ export const addNodes = (json, state) => {
|
||||
const removePosts = (json, state: GraphState): GraphState => {
|
||||
const _remove = (graph, index) => {
|
||||
const child = graph.get(index[0]);
|
||||
if(!child) {
|
||||
return graph;
|
||||
}
|
||||
if (index.length === 1) {
|
||||
if (child) {
|
||||
return graph.set(index[0], {
|
||||
post: child.post.hash || '',
|
||||
children: child.children
|
||||
});
|
||||
}
|
||||
return graph.set(index[0], {
|
||||
post: child.post.hash || '',
|
||||
children: child.children
|
||||
});
|
||||
} else {
|
||||
if (child) {
|
||||
_remove(child.children, index.slice(1));
|
||||
return graph.set(index[0], child);
|
||||
} else {
|
||||
const child = graph.get(index[0]);
|
||||
if (child) {
|
||||
return graph.set(index[0], produce((draft: any) => {
|
||||
draft.children = _remove(draft.children, index.slice(1));
|
||||
}));
|
||||
}
|
||||
return graph;
|
||||
}
|
||||
const node = { ...child, children: _remove(child.children, index.slice(1)) };
|
||||
return graph.set(index[0], node);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { cite, Content, Post } from '@urbit/api';
|
||||
import { cite, Content, Post, removeDmMessage } from '@urbit/api';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import _ from 'lodash';
|
||||
import bigInt from 'big-integer';
|
||||
@ -11,6 +11,7 @@ import useHarkState, { useHarkDm } from '~/logic/state/hark';
|
||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||
import { ChatPane } from './components/ChatPane';
|
||||
import shallow from 'zustand/shallow';
|
||||
import airlock from '~/logic/api';
|
||||
|
||||
interface DmResourceProps {
|
||||
ship: string;
|
||||
@ -121,6 +122,9 @@ export function DmResource(props: DmResourceProps) {
|
||||
[ship, addDmMessage]
|
||||
);
|
||||
|
||||
const onDelete = useCallback((msg: Post) => {
|
||||
airlock.poke(removeDmMessage(`~${window.ship}`, msg.index));
|
||||
}, []);
|
||||
return (
|
||||
<Col width="100%" height="100%" overflow="hidden">
|
||||
<Row
|
||||
@ -169,6 +173,7 @@ export function DmResource(props: DmResourceProps) {
|
||||
onReply={quoteReply}
|
||||
fetchMessages={fetchMessages}
|
||||
dismissUnread={dismissUnread}
|
||||
onDelete={onDelete}
|
||||
getPermalink={() => undefined}
|
||||
isAdmin={false}
|
||||
onSubmit={onSubmit}
|
||||
|
@ -232,6 +232,7 @@ const ChatEditor = React.forwardRef<CodeMirrorShim, ChatEditorProps>(({ inCodeMo
|
||||
fontFamily={inCodeMode ? 'Source Code Pro' : 'Inter'}
|
||||
fontSize={1}
|
||||
lineHeight="tall"
|
||||
value={message}
|
||||
rows={1}
|
||||
style={{ width: '100%', background: 'transparent', color: 'currentColor' }}
|
||||
placeholder={inCodeMode ? 'Code...' : 'Message...'}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { BaseImage, Box, Row, Text } from '@tlon/indigo-react';
|
||||
import { BaseImage, Row, Text, Button } from '@tlon/indigo-react';
|
||||
import { allowGroup, allowShips, Contact, share } from '@urbit/api';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
@ -61,14 +61,15 @@ const ShareProfile = (props: ShareProfileProps): ReactElement | null => {
|
||||
borderBottom={1}
|
||||
borderColor="lightGray"
|
||||
flexShrink={0}
|
||||
px="3"
|
||||
>
|
||||
<Row pl={3} alignItems="center">
|
||||
<Row alignItems="center">
|
||||
{image}
|
||||
<Text verticalAlign="middle" pl={2}>Share private profile?</Text>
|
||||
</Row>
|
||||
<Box pr={2} onClick={onClick}>
|
||||
<Text color="blue" bold cursor="pointer">Share</Text>
|
||||
</Box>
|
||||
<Button primary onClick={onClick}>
|
||||
Share
|
||||
</Button>
|
||||
</Row>
|
||||
) : null;
|
||||
};
|
||||
|
@ -55,8 +55,10 @@ export function LinkBlocks(props: LinkBlocksProps) {
|
||||
}, [association.resource]);
|
||||
|
||||
const orm = useMemo(() => {
|
||||
const nodes = [null, ...Array.from(props.graph)];
|
||||
|
||||
const graph = Array.from(props.graph).filter(
|
||||
([idx, node]) => typeof node?.post !== 'string'
|
||||
);
|
||||
const nodes = [null, ...graph];
|
||||
const chunks = _.chunk(nodes, colCount);
|
||||
return new BigIntOrderedMap<[bigInt.BigInteger, GraphNode][]>().gas(
|
||||
chunks.reverse().map((chunk, i) => {
|
||||
|
@ -39,7 +39,6 @@ export function PostForm(props: PostFormProps) {
|
||||
validationSchema={formSchema}
|
||||
initialValues={initial}
|
||||
onSubmit={onSubmit}
|
||||
validateOnBlur
|
||||
>
|
||||
<Form style={{ display: 'contents' }}>
|
||||
<Row flexShrink={0} flexDirection={['column-reverse', 'row']} mb={4} gapX={4} justifyContent='space-between'>
|
||||
|
@ -44,7 +44,11 @@ function getAdjacentId(
|
||||
): BigInteger | null {
|
||||
const children = Array.from(graph);
|
||||
const i = children.findIndex(([index]) => index.eq(child));
|
||||
const target = children[backwards ? i + 1 : i - 1];
|
||||
let idx = backwards ? i + 1 : i - 1;
|
||||
let target = children[idx];
|
||||
while(typeof target?.[1]?.post === 'string') {
|
||||
target = children[backwards ? idx++ : idx--];
|
||||
}
|
||||
return target?.[0] || null;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import { Graph, Group } from '@urbit/api';
|
||||
import React from 'react';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import { NotePreview } from './NotePreview';
|
||||
|
||||
interface NotebookPostsProps {
|
||||
@ -19,7 +18,7 @@ export function NotebookPosts(props: NotebookPostsProps) {
|
||||
<Col>
|
||||
{Array.from(props.graph || []).map(
|
||||
([date, node]) =>
|
||||
node && (
|
||||
node && typeof node?.post !== 'string' && (
|
||||
<NotePreview
|
||||
key={date.toString()}
|
||||
host={props.host}
|
||||
|
@ -336,6 +336,26 @@ export const removePosts = (
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Remove a DM message from our inbox
|
||||
*
|
||||
* @remarks
|
||||
* This does not remove the message from the recipients inbox
|
||||
*/
|
||||
export const removeDmMessage = (
|
||||
our: Patp,
|
||||
index: string
|
||||
): Poke<any> => ({
|
||||
app: 'graph-store',
|
||||
mark: `graph-update-${GRAPH_UPDATE_VERSION}`,
|
||||
json: {
|
||||
'remove-posts': {
|
||||
resource: { ship: our, name: 'dm-inbox' },
|
||||
indices: [index]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Send a DM to a ship
|
||||
*
|
||||
|
6
pkg/npm/http-api/babel.config.js
Normal file
6
pkg/npm/http-api/babel.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', {targets: {node: 'current'}}],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
};
|
194
pkg/npm/http-api/jest.config.js
Normal file
194
pkg/npm/http-api/jest.config.js
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* For a detailed explanation regarding each configuration property and type check, visit:
|
||||
* https://jestjs.io/docs/configuration
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
automock: false,
|
||||
|
||||
// Stop running tests after `n` failures
|
||||
// bail: 0,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "/private/var/folders/7w/hvrpvq7978bbb9kwkbhsn6rr0000gn/T/jest_dx",
|
||||
|
||||
// Automatically clear mock calls and instances between every test
|
||||
clearMocks: true,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
collectCoverage: true,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
// collectCoverageFrom: undefined,
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
coverageDirectory: "coverage",
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// Indicates which provider should be used to instrument code for coverage
|
||||
// coverageProvider: "babel",
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
// coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
// "lcov",
|
||||
// "clover"
|
||||
// ],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: undefined,
|
||||
|
||||
// A path to a custom dependency extractor
|
||||
// dependencyExtractor: undefined,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// Force coverage collection from ignored files using an array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
// globalSetup: undefined,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: undefined,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
// globals: {},
|
||||
|
||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||
maxWorkers: 1,
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
// moduleFileExtensions: [
|
||||
// "js",
|
||||
// "jsx",
|
||||
// "ts",
|
||||
// "tsx",
|
||||
// "json",
|
||||
// "node"
|
||||
// ],
|
||||
|
||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||
// moduleNameMapper: {},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
// preset: undefined,
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: undefined,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state between every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: undefined,
|
||||
|
||||
// Automatically restore mock state between every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
// rootDir: undefined,
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
// roots: [
|
||||
// "<rootDir>"
|
||||
// ],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
setupFiles: ['./setupEnv.js'],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
// setupFilesAfterEnv: [],
|
||||
|
||||
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
||||
// slowTestThreshold: 5,
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
testEnvironment: "jsdom",
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
// testMatch: [
|
||||
// "**/__tests__/**/*.[jt]s?(x)",
|
||||
// "**/?(*.)+(spec|test).[tj]s?(x)"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
// testRegex: [],
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: undefined,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jest-circus/runner",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
testURL: "http://localhost",
|
||||
|
||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||
// timers: "real",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
// transform: undefined,
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
// transformIgnorePatterns: [
|
||||
// "/node_modules/",
|
||||
// "\\.pnp\\.[^\\/]+$"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: undefined,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
};
|
BIN
pkg/npm/http-api/package-lock.json
generated
BIN
pkg/npm/http-api/package-lock.json
generated
Binary file not shown.
@ -15,7 +15,7 @@
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "echo \"No test specified\" && exit 0",
|
||||
"test": "jest",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"prepare": "npm run build",
|
||||
"watch": "tsc -p tsconfig.json --watch",
|
||||
@ -29,25 +29,34 @@
|
||||
},
|
||||
"author": "",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.3",
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
|
||||
"@babel/preset-env": "^7.14.7",
|
||||
"@babel/preset-typescript": "^7.12.1",
|
||||
"@types/browser-or-node": "^1.2.0",
|
||||
"@types/eventsource": "^1.1.5",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/react": "^16.9.56",
|
||||
"@typescript-eslint/eslint-plugin": "^4.7.0",
|
||||
"@typescript-eslint/parser": "^4.7.0",
|
||||
"babel-jest": "^27.0.6",
|
||||
"babel-loader": "^8.2.1",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"cross-fetch": "^3.1.4",
|
||||
"event-target-polyfill": "0.0.3",
|
||||
"fast-text-encoding": "^1.0.3",
|
||||
"jest": "^27.0.6",
|
||||
"onchange": "^7.1.0",
|
||||
"tslib": "^2.0.3",
|
||||
"typescript": "^3.9.7",
|
||||
"util": "^0.12.3",
|
||||
"web-streams-polyfill": "^3.0.3",
|
||||
"webpack": "^5.4.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.11.0"
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"yet-another-abortcontroller-polyfill": "0.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
@ -55,7 +64,6 @@
|
||||
"browser-or-node": "^1.3.0",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
"buffer": "^6.0.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"stream-http": "^3.1.1"
|
||||
}
|
||||
|
8
pkg/npm/http-api/setupEnv.js
Normal file
8
pkg/npm/http-api/setupEnv.js
Normal file
@ -0,0 +1,8 @@
|
||||
require('event-target-polyfill');
|
||||
require('yet-another-abortcontroller-polyfill');
|
||||
require('cross-fetch/polyfill');
|
||||
require('fast-text-encoding');
|
||||
require('web-streams-polyfill');
|
||||
|
||||
global.ReadableStream = require('web-streams-polyfill').ReadableStream;
|
||||
|
@ -165,6 +165,11 @@ export class Urbit {
|
||||
* Initializes the SSE pipe for the appropriate channel.
|
||||
*/
|
||||
eventSource(): Promise<void> {
|
||||
if(this.lastEventId === 0) {
|
||||
// Can't receive events until the channel is open
|
||||
this.skipDebounce = true;
|
||||
return this.poke({ app: 'hood', mark: 'helm-hi', json: 'Opening API channel' }).then(() => {});
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.sseClientInitialized) {
|
||||
const sseOptions: SSEOptions = {
|
||||
@ -175,10 +180,6 @@ export class Urbit {
|
||||
} else if (isNode) {
|
||||
sseOptions.headers.Cookie = this.cookie;
|
||||
}
|
||||
if (this.lastEventId === 0) {
|
||||
// Can't receive events until the channel is open
|
||||
this.poke({ app: 'hood', mark: 'helm-hi', json: 'Opening API channel' });
|
||||
}
|
||||
fetchEventSource(this.channelUrl, {
|
||||
...this.fetchOptions,
|
||||
openWhenHidden: true,
|
||||
@ -236,6 +237,7 @@ export class Urbit {
|
||||
funcs.quit(data);
|
||||
this.outstandingSubscriptions.delete(data.id);
|
||||
} else {
|
||||
console.log([...this.outstandingSubscriptions.keys()]);
|
||||
console.log('Unrecognized response', data);
|
||||
}
|
||||
}
|
||||
@ -329,7 +331,8 @@ export class Urbit {
|
||||
private outstandingJSON: Message[] = [];
|
||||
|
||||
private debounceTimer: NodeJS.Timeout = null;
|
||||
private debounceInterval = 500;
|
||||
private debounceInterval = 10;
|
||||
private skipDebounce = false;
|
||||
private calm = true;
|
||||
|
||||
private sendJSONtoChannel(json: Message): Promise<boolean | void> {
|
||||
@ -351,11 +354,14 @@ export class Urbit {
|
||||
return resolve(false);
|
||||
}
|
||||
try {
|
||||
await fetch(this.channelUrl, {
|
||||
const response = await fetch(this.channelUrl, {
|
||||
...this.fetchOptions,
|
||||
method: 'PUT',
|
||||
body
|
||||
});
|
||||
if(!response.ok) {
|
||||
throw new Error('failed to PUT');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
json.forEach(failed => this.outstandingJSON.push(failed));
|
||||
@ -367,7 +373,7 @@ export class Urbit {
|
||||
}
|
||||
this.calm = true;
|
||||
if (!this.sseClientInitialized) {
|
||||
this.eventSource(); // We can open the channel for subscriptions once we've sent data over it
|
||||
this.eventSource().then(resolve); // We can open the channel for subscriptions once we've sent data over it
|
||||
}
|
||||
resolve(true);
|
||||
} else {
|
||||
@ -376,6 +382,10 @@ export class Urbit {
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
if(this.skipDebounce) {
|
||||
process();
|
||||
this.skipDebounce = false;
|
||||
}
|
||||
|
||||
this.debounceTimer = setTimeout(process, this.debounceInterval);
|
||||
|
||||
|
@ -1,8 +1,167 @@
|
||||
import Urbit from '../src';
|
||||
import { Readable } from 'streams';
|
||||
|
||||
describe('blah', () => {
|
||||
it('works', () => {
|
||||
const connection = new Urbit('~sampel-palnet', '+code');
|
||||
expect(connection).toEqual(2);
|
||||
function fakeSSE(messages = [], timeout = 0) {
|
||||
const ourMessages = [...messages];
|
||||
const enc = new TextEncoder();
|
||||
return new ReadableStream({
|
||||
start(controller) {
|
||||
const interval = setInterval(() => {
|
||||
let message = ':\n';
|
||||
if (ourMessages.length > 0) {
|
||||
message = ourMessages.shift();
|
||||
}
|
||||
|
||||
controller.enqueue(enc.encode(message));
|
||||
}, 50);
|
||||
|
||||
if (timeout > 0) {
|
||||
setTimeout(() => {
|
||||
controller.close();
|
||||
interval;
|
||||
}, timeout);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const ship = '~sampel-palnet';
|
||||
let eventId = 0;
|
||||
function event(data: any) {
|
||||
return `id:${eventId++}\ndata:${JSON.stringify(data)}\n\n`;
|
||||
}
|
||||
|
||||
function fact(id: number, data: any) {
|
||||
return event({
|
||||
response: 'diff',
|
||||
id,
|
||||
json: data,
|
||||
});
|
||||
}
|
||||
|
||||
function ack(id: number, err = false) {
|
||||
const res = err ? { err: 'Error' } : { ok: true };
|
||||
return event({ id, response: 'poke', ...res });
|
||||
}
|
||||
const fakeFetch = (body) => () =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
body: body(),
|
||||
});
|
||||
|
||||
const wait = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
||||
|
||||
process.on('unhandledRejection', () => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
describe('Initialisation', () => {
|
||||
let airlock: Urbit;
|
||||
let fetchSpy;
|
||||
beforeEach(() => {
|
||||
airlock = new Urbit('', '+code');
|
||||
airlock.debounceInterval = 10;
|
||||
});
|
||||
afterEach(() => {
|
||||
fetchSpy.mockReset();
|
||||
});
|
||||
it('should poke & connect upon a 200', async () => {
|
||||
airlock.onOpen = jest.fn();
|
||||
fetchSpy = jest.spyOn(window, 'fetch');
|
||||
fetchSpy
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ ok: true, body: fakeSSE() })
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ ok: true, body: fakeSSE() })
|
||||
);
|
||||
await airlock.eventSource();
|
||||
|
||||
expect(airlock.onOpen).toHaveBeenCalled();
|
||||
}, 500);
|
||||
it('should handle failures', async () => {
|
||||
fetchSpy = jest.spyOn(window, 'fetch');
|
||||
fetchSpy
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve({ ok: false, body: fakeSSE() })
|
||||
)
|
||||
airlock.onError = jest.fn();
|
||||
try {
|
||||
await airlock.eventSource();
|
||||
wait(100);
|
||||
} catch (e) {
|
||||
expect(airlock.onError).toHaveBeenCalled();
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
|
||||
describe('subscription', () => {
|
||||
let airlock: Urbit;
|
||||
let fetchSpy: jest.SpyInstance;
|
||||
beforeEach(() => {
|
||||
eventId = 1;
|
||||
});
|
||||
afterEach(() => {
|
||||
fetchSpy.mockReset();
|
||||
});
|
||||
|
||||
it('should subscribe', async () => {
|
||||
fetchSpy = jest.spyOn(window, 'fetch');
|
||||
airlock = new Urbit('', '+code');
|
||||
airlock.onOpen = jest.fn();
|
||||
const params = {
|
||||
app: 'app',
|
||||
path: '/path',
|
||||
err: jest.fn(),
|
||||
event: jest.fn(),
|
||||
quit: jest.fn(),
|
||||
};
|
||||
const firstEv = 'one';
|
||||
const secondEv = 'two';
|
||||
const events = (id) => [fact(id, firstEv), fact(id, secondEv)];
|
||||
fetchSpy.mockImplementation(fakeFetch(() => fakeSSE(events(1))));
|
||||
|
||||
await airlock.subscribe(params);
|
||||
await wait(600);
|
||||
|
||||
expect(airlock.onOpen).toBeCalled();
|
||||
expect(params.event).toHaveBeenNthCalledWith(1, firstEv);
|
||||
expect(params.event).toHaveBeenNthCalledWith(2, secondEv);
|
||||
}, 800);
|
||||
it('should poke', async () => {
|
||||
fetchSpy = jest.spyOn(window, 'fetch');
|
||||
airlock = new Urbit('', '+code');
|
||||
airlock.onOpen = jest.fn();
|
||||
fetchSpy.mockImplementation(fakeFetch(() => fakeSSE([ack(1)])));
|
||||
const params = {
|
||||
app: 'app',
|
||||
mark: 'mark',
|
||||
json: { poke: 1 },
|
||||
onSuccess: jest.fn(),
|
||||
onError: jest.fn(),
|
||||
};
|
||||
await airlock.poke(params);
|
||||
await wait(300);
|
||||
expect(params.onSuccess).toHaveBeenCalled();
|
||||
}, 800);
|
||||
|
||||
it('should nack poke', async () => {
|
||||
fetchSpy = jest.spyOn(window, 'fetch');
|
||||
airlock = new Urbit('', '+code');
|
||||
airlock.onOpen = jest.fn();
|
||||
fetchSpy.mockImplementation(fakeFetch(() => fakeSSE([ack(1, true)])));
|
||||
const params = {
|
||||
app: 'app',
|
||||
mark: 'mark',
|
||||
json: { poke: 1 },
|
||||
onSuccess: jest.fn(),
|
||||
onError: jest.fn(),
|
||||
};
|
||||
try {
|
||||
await airlock.poke(params);
|
||||
await wait(300);
|
||||
} catch (e) {
|
||||
expect(params.onError).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user