Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/t3chguy/flaky-tests-10jan' into …
Browse files Browse the repository at this point in the history
…t3chguy/flaky-tests-10jan
  • Loading branch information
t3chguy committed Jan 13, 2025
2 parents 7de0ddd + c25a2f4 commit 0aea827
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 83 deletions.
6 changes: 0 additions & 6 deletions .github/workflows/static_analysis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,3 @@ jobs:

- name: Run linter
run: "yarn run lint:knip"

- name: Install Deps
run: "scripts/layered.sh"

- name: Dead Code Analysis
run: "yarn run analyse:unused-exports"
13 changes: 4 additions & 9 deletions knip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ export default {
"playwright/**",
"test/**",
"res/decoder-ring/**",
],
project: ["**/*.{js,ts,jsx,tsx}"],
ignore: [
"docs/**",
"res/jitsi_external_api.min.js",
"docs/**",
// Used by jest
"__mocks__/maplibre-gl.js",
],
project: ["**/*.{js,ts,jsx,tsx}"],
ignore: [
// Keep for now
"src/hooks/useLocalStorageState.ts",
"src/components/views/elements/InfoTooltip.tsx",
Expand All @@ -37,13 +37,8 @@ export default {
// False positive
"sw.js",
// Used by webpack
"buffer",
"process",
"util",
// Used by workflows
"ts-prune",
// Required due to bug in bloom-filters https://github.com/Callidon/bloom-filters/issues/75
"@types/seedrandom",
],
ignoreBinaries: [
// Used in scripts & workflows
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
"test:playwright:screenshots:build": "docker build playwright -t element-web-playwright",
"test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -e CI -v $(pwd):/work/ -v $(node -e 'console.log(require(`path`).dirname(require.resolve(`matrix-js-sdk/package.json`)))'):/work/node_modules/matrix-js-sdk -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it element-web-playwright --grep @screenshot --project=Chrome",
"coverage": "yarn test --coverage",
"analyse:unused-exports": "ts-node ./scripts/analyse_unused_exports.ts",
"analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp",
"update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js"
},
Expand Down Expand Up @@ -287,7 +286,6 @@
"terser-webpack-plugin": "^5.3.9",
"testcontainers": "^10.16.0",
"ts-node": "^10.9.1",
"ts-prune": "^0.10.3",
"typescript": "5.7.2",
"util": "^0.12.5",
"web-streams-polyfill": "^4.0.0",
Expand Down
3 changes: 3 additions & 0 deletions playwright/e2e/login/login-consent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ test.use({
...credentials,
displayName,
});

// Restart the homeserver to wipe its in-memory db so we can reuse the same user ID without cross-signing prompts
await homeserver.restart();
},
});

Expand Down
1 change: 1 addition & 0 deletions playwright/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { OAuthServer } from "./plugins/oauth_server";

export interface TestFixtures {
mailhogClient: mailhog.API;

// Set in legacyOAuthHomeserver only
oAuthServer?: OAuthServer;
}
Expand Down
1 change: 0 additions & 1 deletion src/Lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ dis.register((payload) => {
let sessionLockStolen = false;

// this is exposed solely for unit tests.
// ts-prune-ignore-next
export function setSessionLockNotStolen(): void {
sessionLockStolen = false;
}
Expand Down
178 changes: 123 additions & 55 deletions src/components/views/elements/EventListSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ Please see LICENSE files in the repository root for full details.
*/

import React, { ComponentProps, ReactNode } from "react";
import { MatrixEvent, RoomMember, EventType } from "matrix-js-sdk/src/matrix";
import { EventType, MatrixEvent, MatrixEventEvent, RoomMember } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { throttle } from "lodash";

import { _t } from "../../../languageHandler";
import { formatList } from "../../../utils/FormattingUtils";
Expand All @@ -22,6 +23,8 @@ import { Layout } from "../../../settings/enums/Layout";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import AccessibleButton from "./AccessibleButton";
import RoomContext from "../../../contexts/RoomContext";
import { arrayHasDiff } from "../../../utils/arrays.ts";
import { objectHasDiff } from "../../../utils/objects.ts";

const onPinnedMessagesClick = (): void => {
RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false);
Expand Down Expand Up @@ -69,9 +72,14 @@ enum TransitionType {

const SEP = ",";

export default class EventListSummary extends React.Component<
IProps & Required<Pick<IProps, "summaryLength" | "threshold" | "avatarsMaxLength" | "layout">>
> {
type Props = IProps & Required<Pick<IProps, "summaryLength" | "threshold" | "avatarsMaxLength" | "layout">>;

interface State {
userEvents: Record<string, IUserEvents[]>;
summaryMembers: RoomMember[];
}

export default class EventListSummary extends React.Component<Props, State> {
public static contextType = RoomContext;
declare public context: React.ContextType<typeof RoomContext>;

Expand All @@ -82,15 +90,122 @@ export default class EventListSummary extends React.Component<
layout: Layout.Group,
};

public shouldComponentUpdate(nextProps: IProps): boolean {
public constructor(props: Props) {
super(props);

this.state = this.generateState();
}

private generateState(): State {
const eventsToRender = this.props.events;

// Map user IDs to latest Avatar Member. ES6 Maps are ordered by when the key was created,
// so this works perfectly for us to match event order whilst storing the latest Avatar Member
const latestUserAvatarMember = new Map<string, RoomMember>();

// Object mapping user IDs to an array of IUserEvents
const userEvents: Record<string, IUserEvents[]> = {};
eventsToRender.forEach((e, index) => {
const type = e.getType();

let userKey = e.getSender()!;
if (e.isState() && type === EventType.RoomThirdPartyInvite) {
userKey = e.getContent().display_name;
} else if (e.isState() && type === EventType.RoomMember) {
userKey = e.getStateKey()!;
} else if (e.isRedacted() && e.getUnsigned()?.redacted_because) {
userKey = e.getUnsigned().redacted_because!.sender;
}

// Initialise a user's events
if (!userEvents[userKey]) {
userEvents[userKey] = [];
}

let displayName = userKey;
if (e.isRedacted()) {
const sender = this.context?.room?.getMember(userKey);
if (sender) {
displayName = sender.name;
latestUserAvatarMember.set(userKey, sender);
}
} else if (e.target && TARGET_AS_DISPLAY_NAME_EVENTS.includes(type as EventType)) {
displayName = e.target.name;
latestUserAvatarMember.set(userKey, e.target);
} else if (e.sender && type !== EventType.RoomThirdPartyInvite) {
displayName = e.sender.name;
latestUserAvatarMember.set(userKey, e.sender);
}

userEvents[userKey].push({
mxEvent: e,
displayName,
index: index,
});
});

return {
userEvents,
summaryMembers: Array.from(latestUserAvatarMember.values()),
};
}

public componentDidMount(): void {
this.bindSentinelListeners(this.props.events);
}

public componentDidUpdate(prevProps: Readonly<Props>): void {
if (prevProps.events !== this.props.events) {
this.unbindSentinelListeners(prevProps.events);
this.bindSentinelListeners(this.props.events);
this.setState(this.generateState());
}
}

public componentWillUnmount(): void {
this.unbindSentinelListeners(this.props.events);
}

private bindSentinelListeners(events: MatrixEvent[]): void {
for (const event of events) {
event.on(MatrixEventEvent.SentinelUpdated, this.onEventSentinelUpdated);
}
}

private unbindSentinelListeners(events: MatrixEvent[]): void {
for (const event of events) {
event.on(MatrixEventEvent.SentinelUpdated, this.onEventSentinelUpdated);
}
}

private onEventSentinelUpdated = throttle(
(): void => {
console.log("@@ SENTINEL UPDATED");
this.setState(this.generateState());
},
500,
{ leading: true, trailing: true },
);

public shouldComponentUpdate(nextProps: Props, nextState: State): boolean {
// Update if
// - The number of summarised events has changed
// - or if the summary is about to toggle to become collapsed
// - or if there are fewEvents, meaning the child eventTiles are shown as-is
// - or if the summary members have changed
// - or if the one of IUserEvents within userEvents have changed
return (
nextProps.events.length !== this.props.events.length ||
nextProps.events.length < this.props.threshold ||
nextProps.layout !== this.props.layout
nextProps.layout !== this.props.layout ||
arrayHasDiff(nextState.summaryMembers, this.state.summaryMembers) ||
arrayHasDiff(Object.values(nextState.userEvents), Object.values(this.state.userEvents)) ||
Object.keys(nextState.userEvents).length !== Object.keys(this.state.userEvents).length ||
Object.keys(nextState.userEvents).some((userId) =>
nextState.userEvents[userId].some((event, i) =>
objectHasDiff(event, this.state.userEvents[userId]?.[i] ?? {}),
),
)
);
}

Expand Down Expand Up @@ -492,54 +607,7 @@ export default class EventListSummary extends React.Component<
}

public render(): React.ReactNode {
const eventsToRender = this.props.events;

// Map user IDs to latest Avatar Member. ES6 Maps are ordered by when the key was created,
// so this works perfectly for us to match event order whilst storing the latest Avatar Member
const latestUserAvatarMember = new Map<string, RoomMember>();

// Object mapping user IDs to an array of IUserEvents
const userEvents: Record<string, IUserEvents[]> = {};
eventsToRender.forEach((e, index) => {
const type = e.getType();

let userKey = e.getSender()!;
if (e.isState() && type === EventType.RoomThirdPartyInvite) {
userKey = e.getContent().display_name;
} else if (e.isState() && type === EventType.RoomMember) {
userKey = e.getStateKey()!;
} else if (e.isRedacted() && e.getUnsigned()?.redacted_because) {
userKey = e.getUnsigned().redacted_because!.sender;
}

// Initialise a user's events
if (!userEvents[userKey]) {
userEvents[userKey] = [];
}

let displayName = userKey;
if (e.isRedacted()) {
const sender = this.context?.room?.getMember(userKey);
if (sender) {
displayName = sender.name;
latestUserAvatarMember.set(userKey, sender);
}
} else if (e.target && TARGET_AS_DISPLAY_NAME_EVENTS.includes(type as EventType)) {
displayName = e.target.name;
latestUserAvatarMember.set(userKey, e.target);
} else if (e.sender && type !== EventType.RoomThirdPartyInvite) {
displayName = e.sender.name;
latestUserAvatarMember.set(userKey, e.sender);
}

userEvents[userKey].push({
mxEvent: e,
displayName,
index: index,
});
});

const aggregate = this.getAggregate(userEvents);
const aggregate = this.getAggregate(this.state.userEvents);

// Sort types by order of lowest event index within sequence
const orderedTransitionSequences = Object.keys(aggregate.names).sort(
Expand All @@ -554,7 +622,7 @@ export default class EventListSummary extends React.Component<
onToggle={this.props.onToggle}
startExpanded={this.props.startExpanded}
children={this.props.children}
summaryMembers={[...latestUserAvatarMember.values()]}
summaryMembers={this.state.summaryMembers}
layout={this.props.layout}
summaryText={this.generateSummary(aggregate.names, orderedTransitionSequences)}
/>
Expand Down
15 changes: 14 additions & 1 deletion src/components/views/messages/TextualEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/

import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";

import RoomContext from "../../../contexts/RoomContext";
import * as TextForEvent from "../../../TextForEvent";
Expand All @@ -21,6 +21,19 @@ export default class TextualEvent extends React.Component<IProps> {
public static contextType = RoomContext;
declare public context: React.ContextType<typeof RoomContext>;

public componentDidMount(): void {
this.props.mxEvent.on(MatrixEventEvent.SentinelUpdated, this.onEventSentinelUpdated);
}
public componentWillUnmount(): void {
this.props.mxEvent.off(MatrixEventEvent.SentinelUpdated, this.onEventSentinelUpdated);
}

private onEventSentinelUpdated = (): void => {
// XXX: this is crap, but we don't have a better way to force a re-render
// Many TextForEvent handlers render parts of `event.sender` and `event.target` so ensure they are updated
this.forceUpdate();
};

public render(): React.ReactNode {
const text = TextForEvent.textForEvent(
this.props.mxEvent,
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/usePinnedEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ async function fetchPinnedEvent(room: Room, pinnedEventId: string, cli: MatrixCl
const senderUserId = event.getSender();
if (senderUserId && PinningUtils.isUnpinnable(event)) {
// Inject sender information
event.sender = room.getMember(senderUserId);
event.setMetadata(room.currentState, false);
// Also inject any edits we've found
if (edit) event.makeReplaced(edit);

Expand Down
2 changes: 0 additions & 2 deletions src/utils/arrays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export function arrayFastResample(input: number[], points: number): number[] {
* @param {number} points The number of samples to end up with.
* @returns {number[]} The resampled array.
*/
// ts-prune-ignore-next
export function arraySmoothingResample(input: number[], points: number): number[] {
if (input.length === points) return input; // short-circuit a complicated call

Expand Down Expand Up @@ -92,7 +91,6 @@ export function arraySmoothingResample(input: number[], points: number): number[
* @param {number} newMax The maximum value to scale to.
* @returns {number[]} The rescaled array.
*/
// ts-prune-ignore-next
export function arrayRescale(input: number[], newMin: number, newMax: number): number[] {
const min: number = Math.min(...input);
const max: number = Math.max(...input);
Expand Down
7 changes: 1 addition & 6 deletions src/utils/exportUtils/Exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,7 @@ export default abstract class Exporter {
}

protected setEventMetadata(event: MatrixEvent): MatrixEvent {
const roomState = this.room.currentState;
const sender = event.getSender();
event.sender = (!!sender && roomState?.getSentinelMember(sender)) || null;
if (event.getType() === "m.room.member") {
event.target = roomState?.getSentinelMember(event.getStateKey()!) ?? null;
}
event.setMetadata(this.room.currentState, false);
return event;
}

Expand Down
Loading

0 comments on commit 0aea827

Please sign in to comment.