Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ concurrency:

jobs:
eslint:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04

strategy:
matrix:
Expand Down Expand Up @@ -42,7 +42,7 @@ jobs:
needs: eslint

timeout-minutes: 60
runs-on: ubuntu-latest
runs-on: ubuntu-22.04

strategy:
matrix:
Expand Down Expand Up @@ -88,7 +88,7 @@ jobs:
needs: [eslint, tests]

timeout-minutes: 60
runs-on: ubuntu-latest
runs-on: ubuntu-22.04

strategy:
matrix:
Expand All @@ -114,10 +114,10 @@ jobs:
key: ${{ runner.OS }}-node-${{ matrix.node-version }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.OS }}-node-${{ matrix.node-version }}-yarn-

- name: Install Playwright Browsers
run: npx playwright install chromium firefox --with-deps

- name: Build e2e example app
run: yarn e2e:build

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"@remote-ui/rpc": "^1.4.5"
},
"peerDependencies": {
"vue": "^3.4.15"
"vue": "^3.4"
},
"devDependencies": {
"@babel/core": "^7.23.9",
Expand All @@ -72,7 +72,7 @@
"@typescript-eslint/parser": "^6.19.1",
"@vitejs/plugin-vue": "^5.0.4",
"@vitest/coverage-istanbul": "^2.1.6",
"@vue/compiler-sfc": "^3.4.15",
"@vue/compiler-sfc": "^3.5.13",
"@vue/language-server": "^2.1.10",
"cors": "^2.8.5",
"eslint": "^8.56.0",
Expand All @@ -95,7 +95,7 @@
"vite": "^5.4.11",
"vite-plugin-dts": "^4.3.0",
"vitest": "^2.1.5",
"vue": "^3.4.15"
"vue": "^3.5.13"
},
"publishConfig": {
"access": "public"
Expand Down
36 changes: 20 additions & 16 deletions src/dom/remote/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,24 +298,28 @@
throw new Error('Cannot insert a node that was not created by this remote root')
}

const currentParent = child.parent
const currentIndex = currentParent?.children.indexOf(child) ?? -1
if (before && before.id === child.id) return
if (before && !parent.children.includes(before)) {
throw new DOMException(

Check warning on line 303 in src/dom/remote/context.ts

View check run for this annotation

Codecov / codecov/patch

src/dom/remote/context.ts#L303

Added line #L303 was not covered by tests
'Cannot add a child before an element that is not a child of the target parent.',
'HierarchyRequestError'
)
}

return update(context, parent, (channel) => {
const beforeIndex = before == null
? parent.children.length - 1
: parent.children.indexOf(before)
const oldIndex = parent.children.indexOf(child) ?? -1
const oldParent = child.parent

channel(
ACTION_INSERT_CHILD,
parent.id,
beforeIndex < currentIndex || currentIndex < 0
? beforeIndex
: beforeIndex - 1,
child.serialize(),
currentParent ? currentParent.id : false
)
}, () => insert(context, parent, child, before))
const beforeIndex = before ? parent.children.indexOf(before) : -1

return update(context, parent, (channel) => channel(
ACTION_INSERT_CHILD,
parent.id,
beforeIndex < 0
? parent.children.length
: oldIndex < 0 || oldIndex > beforeIndex ? beforeIndex : beforeIndex - 1,
child.serialize(),
oldParent ? oldParent.id : false
), () => insert(context, parent, child, before))
}

/** @TODO: Объединение удаления нескольких узлов в один запрос */
Expand Down
4 changes: 4 additions & 0 deletions src/dom/remote/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export interface RemoteComment<Root extends RemoteRoot = RemoteRoot> extends Rem
update (text: string): void | Promise<void>;
remove (): void | Promise<void>;
serialize (): SerializedComment;
print (): string;
}

export type RemoteComponentOption<
Expand Down Expand Up @@ -218,6 +219,8 @@ export interface RemoteComponent<
remove (): void | Promise<void>;

serialize (): SerializedComponent<PropertiesOf<Type>>;

print (): string;
}

export interface RemoteFragment<Root extends RemoteRoot = RemoteRoot> extends RemoteNode {
Expand Down Expand Up @@ -280,6 +283,7 @@ export interface RemoteText<Root extends RemoteRoot = RemoteRoot> extends Remote
update (text: string): void | Promise<void>;
serialize (): SerializedText;
remove (): void | Promise<void>;
print (): string;
}

export type UnknownComponent = RemoteComponent<UnknownType>
Expand Down
1 change: 1 addition & 0 deletions src/dom/remote/tree/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const createRemoteComment = <Root extends RemoteRoot>(
),
serialize: () => ({ id, kind: KIND_COMMENT, text: data.text }),
remove: () => node.parent?.removeChild(node),
print: () => `Comment(${data.text})`,
} as RemoteComment<Root>

context.collect(node)
Expand Down
29 changes: 28 additions & 1 deletion src/dom/remote/tree/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ export function createRemoteComponent <R extends RemoteRoot, T extends Supported
properties: data.properties.serializable,
children: data.children.map(c => c.serialize()),
}),

print: () => _print(id, type, data.properties.original as PropertiesOf<T>, data.children),
} as RemoteComponent<T, R>

context.collect(node)
Expand Down Expand Up @@ -237,4 +239,29 @@ function serializeProperty (property: unknown) {
return isRemoteFragment(property)
? property.serialize()
: property
}
}

function _print <R extends RemoteRoot, T extends SupportedBy<R>>(
id: string,
type: T | RemoteComponentDescriptor<T>,
_properties: PropertiesOf<T> | null | undefined,
children: ReadonlyArray<
| RemoteComment<R>
| RemoteComponent<ChildrenOf<T>, R>
| RemoteText<R>
| string
>
) {
const _head = `${typeof type === 'string' ? type : type.type}:${id}`
const _children = children.map(c => typeof c === 'string' ? c : c.print())
const _body = _children.length > 0 ? `\n${_indent(_children.join(',\n'))}\n` : ''

return `${_head}[${_body}]`
}

function _indent (text: string) {
return text
.split('\n')
.map(line => ` ${line}`)
.join('\n')
}
1 change: 1 addition & 0 deletions src/dom/remote/tree/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const createRemoteText = <Root extends RemoteRoot>(
),
serialize: () => ({ id, kind: KIND_TEXT, text: data.text }),
remove: () => node.parent?.removeChild(node),
print: () => `Text(${data.text})`,
} as RemoteText<Root>

context.collect(node)
Expand Down
41 changes: 32 additions & 9 deletions src/vue/host/events.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,49 @@
import type {
SerializedEvent,
SerializedFile,
SerializedDataTransfer,
SerializedEventType,
SerializedInputEvent,
SerializedDragEvent,
SerializedEvent,
SerializedEventType,
SerializedFile,
SerializedFocusEvent,
SerializedInputEvent,
SerializedKeyboardEvent,
SerializedMouseEvent,
SerializedPointerEvent,
SerializedTarget,
SerializedTouch,
SerializedTouchEvent,
SerializedWheelEvent,
} from '~types/events'

export const serializeTarget = (target: EventTarget): SerializedTarget => {
switch (true) {
case target instanceof HTMLInputElement:
case target instanceof HTMLSelectElement:
case target instanceof HTMLTextAreaElement:
return {

Check warning on line 23 in src/vue/host/events.ts

View check run for this annotation

Codecov / codecov/patch

src/vue/host/events.ts#L20-L23

Added lines #L20 - L23 were not covered by tests
value: target.value,
...(target instanceof HTMLInputElement && { checked: target.checked }),
...(target instanceof HTMLSelectElement && {

Check warning on line 26 in src/vue/host/events.ts

View check run for this annotation

Codecov / codecov/patch

src/vue/host/events.ts#L25-L26

Added lines #L25 - L26 were not covered by tests
selectedIndex: target.selectedIndex,
selectedOptions: [...target.selectedOptions].map(option => ({

Check warning on line 28 in src/vue/host/events.ts

View check run for this annotation

Codecov / codecov/patch

src/vue/host/events.ts#L28

Added line #L28 was not covered by tests
value: option.value,
text: option.text,
selected: option.selected,
})),
}),
}
case target instanceof HTMLElement:
return {}
}

return {}

Check warning on line 39 in src/vue/host/events.ts

View check run for this annotation

Codecov / codecov/patch

src/vue/host/events.ts#L39

Added line #L39 was not covered by tests
}

export const serializeBaseEvent = (event: Event): SerializedEvent => {
return {
type: event.type,
target: event.target ? serializeTarget(event.target) : null,
currentTarget: event.currentTarget ? serializeTarget(event.currentTarget) : null,
bubbles: event.bubbles,
cancelable: event.cancelable,
composed: event.composed,
Expand Down Expand Up @@ -55,12 +82,8 @@
export const serializeInputEvent = (event: InputEvent): SerializedInputEvent => {
return {
...serializeBaseEvent(event),
isTrusted: event.isTrusted,
data: event.data,
target: {
value: (event.target as HTMLInputElement | HTMLTextAreaElement).value,
},
}
} as SerializedInputEvent
}

export const serializeFocusEvent = (event: FocusEvent): SerializedFocusEvent => {
Expand Down
2 changes: 1 addition & 1 deletion src/vue/remote/createRemoteRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const nextSibling = <Root extends RemoteRoot = RemoteRoot>(node: Node<Root>) =>
}

const setElementText = <Root extends RemoteRoot = RemoteRoot>(
element: Component<Root>,
element: Root | Component<Root>,
text: string
) => {
const [node] = element.children
Expand Down
9 changes: 8 additions & 1 deletion tests/e2e/events-serializing.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,21 @@ test('serialized InputEvent', async ({ page }) => {

expect(await getSerializedEvent(page, 'input')).toEqual(expect.objectContaining({
type: 'input',
target: {
checked: false,
value: '[email protected]',
},
currentTarget: {
checked: false,
value: '[email protected]',
},
bubbles: true,
cancelable: false,
composed: true,
data: '[email protected]',
defaultPrevented: false,
eventPhase: 2,
isTrusted: true,
target: { value: '[email protected]' },
}))
})

Expand Down
4 changes: 4 additions & 0 deletions tests/integration/dom/remote.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ describe('dom/remote', () => {

expect(comment.id).toEqual('1')
expect(comment.text).toEqual('v-if')
expect(comment.print()).toEqual('Comment(v-if)')
})
})

Expand All @@ -68,6 +69,7 @@ describe('dom/remote', () => {
expect(card.progenitor).toBeNull()
expect(card.parent).toBeNull()
expect(card.children).toEqual([])
expect(card.print()).toEqual('VCard:1[]')
})

test('creates component with children', () => {
Expand All @@ -83,6 +85,7 @@ describe('dom/remote', () => {
expect(card.children).toHaveLength(2)
expect((card.children[0] as UnknownComponent).type).toEqual('VImage')
expect((card.children[1] as UnknownComponent).type).toEqual('VButton')
expect(card.print()).toEqual('VCard:3[\n VImage:1[],\n VButton:2[]\n]')
})

test('creates component with single child', () => {
Expand Down Expand Up @@ -157,6 +160,7 @@ describe('dom/remote', () => {
expect(text.root).toEqual(root)
expect(text.progenitor).toBeNull()
expect(text.parent).toBeNull()
expect(text.print()).toEqual('Text()')
})
})

Expand Down
6 changes: 6 additions & 0 deletions tests/integration/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ describe('vue', () => {
expect(onClick).toHaveBeenCalledTimes(1)
expect(onClick).toHaveBeenCalledWith({
type: 'click',
target: {},
currentTarget: {},
bubbles: true,
button: 0,
cancelable: true,
Expand Down Expand Up @@ -235,6 +237,8 @@ describe('vue', () => {
expect(onClick).toHaveBeenCalledTimes(1)
expect(onClick).toHaveBeenCalledWith({
type: 'click',
target: {},
currentTarget: {},
bubbles: false,
cancelable: false,
composed: false,
Expand Down Expand Up @@ -273,6 +277,8 @@ describe('vue', () => {
expect(onClick).toHaveBeenCalledTimes(1)
expect(onClick).toHaveBeenCalledWith({
type: 'click',
target: {},
currentTarget: {},
bubbles: true,
button: 0,
cancelable: true,
Expand Down
12 changes: 9 additions & 3 deletions types/events.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export interface SerializedEvent {
type: Event['type'];
target: SerializedTarget | null;
currentTarget: SerializedTarget | null;
bubbles: Event['bubbles'];
cancelable: Event['cancelable'];
composed: Event['composed'];
Expand All @@ -23,12 +25,16 @@ export interface SerializedDataTransfer {
files: SerializedFile[];
}

export interface SerializedTarget {}

export interface SerializedInputEventTarget {
value: string;
}

export interface SerializedInputEvent extends SerializedEvent {
isTrusted: InputEvent['isTrusted'];
data: InputEvent['data'];
target: {
value: HTMLInputElement['value'] | HTMLTextAreaElement['value'];
};
target: SerializedInputEventTarget;
}

export interface SerializedDragEvent extends SerializedMouseEvent {
Expand Down
Loading
Loading