resolve undefined this in onClick handler (#32576)
* resolve undefined this in onClick handler * add regression test for bound DisambiguatedProfileViewModel onClick * Update docs * add regression for sender profile click mention insertion * Eslint Fix
This commit is contained in:
@@ -127,6 +127,20 @@ export class FooViewModel extends BaseViewModel<FooViewSnapshot, Props> implemen
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Binding of View Model Actions:
|
||||||
|
|
||||||
|
All view model actions must be defined as arrow functions to ensure they are bound to the class instance.
|
||||||
|
|
||||||
|
Using standard class methods can result in `this` being undefined when the function is passed as a callback (e.g. to a React event handler), which may cause runtime errors.
|
||||||
|
|
||||||
|
Correct pattern:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
public doSomething = (): void => {
|
||||||
|
...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
### `useViewModel` hook
|
### `useViewModel` hook
|
||||||
|
|
||||||
Your view must call this hook with the view-model as argument. Think of this as your view subscribing to the view model.<br>
|
Your view must call this hook with the view-model as argument. Think of this as your view subscribing to the view model.<br>
|
||||||
|
|||||||
@@ -1008,6 +1008,34 @@ test.describe("Timeline", () => {
|
|||||||
await expect(page.getByRole("button", { name: "Show video" })).toBeVisible();
|
await expect(page.getByRole("button", { name: "Show video" })).toBeVisible();
|
||||||
await expect(page.locator("video")).not.toBeVisible();
|
await expect(page.locator("video")).not.toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should insert a mention when clicking sender profile in timeline", async ({
|
||||||
|
page,
|
||||||
|
app,
|
||||||
|
homeserver,
|
||||||
|
room,
|
||||||
|
}) => {
|
||||||
|
const senderDisplayName = "SenderBot";
|
||||||
|
const messageFromSender = "message from sender";
|
||||||
|
|
||||||
|
const bot = new Bot(page, homeserver, {
|
||||||
|
displayName: senderDisplayName,
|
||||||
|
autoAcceptInvites: false,
|
||||||
|
});
|
||||||
|
await bot.prepareClient();
|
||||||
|
await app.client.inviteUser(room.roomId, bot.credentials.userId);
|
||||||
|
await bot.joinRoom(room.roomId);
|
||||||
|
await bot.sendMessage(room.roomId, messageFromSender);
|
||||||
|
|
||||||
|
await app.viewRoomById(room.roomId);
|
||||||
|
|
||||||
|
const senderMessageTile = getEventTilesWithBodies(page).filter({ hasText: messageFromSender }).first();
|
||||||
|
await expect(senderMessageTile).toBeVisible();
|
||||||
|
|
||||||
|
await senderMessageTile.locator(".mx_DisambiguatedProfile").click();
|
||||||
|
|
||||||
|
await expect(app.getComposerField().getByText(senderDisplayName)).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe("message sending", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
test.describe("message sending", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export class DisambiguatedProfileViewModel
|
|||||||
this.snapshot.set(DisambiguatedProfileViewModel.computeSnapshot(this.props));
|
this.snapshot.set(DisambiguatedProfileViewModel.computeSnapshot(this.props));
|
||||||
}
|
}
|
||||||
|
|
||||||
public onClick(evt: MouseEvent<HTMLDivElement>): void {
|
public onClick = (evt: MouseEvent<HTMLDivElement>): void => {
|
||||||
this.props.onClick?.(evt);
|
this.props.onClick?.(evt);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,13 +75,27 @@ describe("DisambiguatedProfileViewModel", () => {
|
|||||||
const subscriber = jest.fn();
|
const subscriber = jest.fn();
|
||||||
|
|
||||||
vm.subscribe(subscriber);
|
vm.subscribe(subscriber);
|
||||||
onClick({} as never);
|
vm.onClick?.({} as never);
|
||||||
|
|
||||||
expect(onClick).toHaveBeenCalledTimes(1);
|
expect(onClick).toHaveBeenCalledTimes(1);
|
||||||
expect(subscriber).not.toHaveBeenCalled();
|
expect(subscriber).not.toHaveBeenCalled();
|
||||||
expect(vm.getSnapshot()).toBe(prevSnapshot);
|
expect(vm.getSnapshot()).toBe(prevSnapshot);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should keep onClick bound when extracted as a callback", () => {
|
||||||
|
const onClick = jest.fn();
|
||||||
|
const vm = new DisambiguatedProfileViewModel({
|
||||||
|
member,
|
||||||
|
fallbackName: "Fallback",
|
||||||
|
onClick,
|
||||||
|
});
|
||||||
|
|
||||||
|
const clickHandler = vm.onClick;
|
||||||
|
|
||||||
|
expect(() => clickHandler?.({} as never)).not.toThrow();
|
||||||
|
expect(onClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it("should emit snapshot update when fallbackName changes", () => {
|
it("should emit snapshot update when fallbackName changes", () => {
|
||||||
const vm = new DisambiguatedProfileViewModel({
|
const vm = new DisambiguatedProfileViewModel({
|
||||||
member: null,
|
member: null,
|
||||||
|
|||||||
Reference in New Issue
Block a user