mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Migrating other 2 files from javascript to typescript (#29435)
* feat: Add initial store configuration for webapp channels * refactor: Convert store/index.js to TypeScript with type definitions * test: Add initial test file for store index * refactor: Convert index.test.js to TypeScript with type annotations * Removing old files * Applying linter fixes * Fixing some of the types errors * fix: Type mock implementation of getState in global_actions.test.ts * test: Add missing GlobalState import in global_actions.test.ts * fix: Resolve TypeScript mock implementation error in global_actions.test.ts * Some fixes * Address CI problems * Installing zen-observable types * Addressing PR review comment * Addressing PR review comment * Addressing PR review comment * Addressing PR review comment * Addressing PR review comment * Simpliying things * Fixing CI * Fixing types
This commit is contained in:
parent
90814564ff
commit
9b87970c99
@ -140,6 +140,7 @@
|
||||
"@types/shallow-equals": "1.0.3",
|
||||
"@types/styled-components": "5.1.32",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/zen-observable": "0.8.7",
|
||||
"copy-webpack-plugin": "11.0.0",
|
||||
"emoji-datasource": "6.1.1",
|
||||
"emoji-datasource-apple": "6.1.1",
|
||||
|
@ -13,6 +13,8 @@ import reduxStore from 'stores/redux_store';
|
||||
import mockStore from 'tests/test_store';
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
|
||||
const getState = jest.mocked(reduxStore.getState);
|
||||
|
||||
jest.mock('actions/views/rhs', () => ({
|
||||
closeMenu: jest.fn(),
|
||||
closeRightHandSide: jest.fn(),
|
||||
@ -64,7 +66,7 @@ describe('actions/global_actions', () => {
|
||||
},
|
||||
});
|
||||
|
||||
reduxStore.getState.mockImplementation(store.getState);
|
||||
getState.mockImplementation(store.getState);
|
||||
|
||||
await redirectUserToDefaultTeam();
|
||||
expect(getHistory().push).toHaveBeenCalledWith('/select_team');
|
||||
@ -138,7 +140,7 @@ describe('actions/global_actions', () => {
|
||||
},
|
||||
});
|
||||
|
||||
reduxStore.getState.mockImplementation(store.getState);
|
||||
getState.mockImplementation(store.getState);
|
||||
|
||||
await redirectUserToDefaultTeam();
|
||||
expect(getHistory().push).toHaveBeenCalledWith('/team2/channels/channel-in-team-2');
|
||||
@ -211,7 +213,7 @@ describe('actions/global_actions', () => {
|
||||
},
|
||||
});
|
||||
|
||||
reduxStore.getState.mockImplementation(store.getState);
|
||||
getState.mockImplementation(store.getState);
|
||||
|
||||
await redirectUserToDefaultTeam();
|
||||
expect(getHistory().push).toHaveBeenCalledWith('/team2/channels/channel-in-team-2');
|
||||
@ -283,7 +285,7 @@ describe('actions/global_actions', () => {
|
||||
},
|
||||
});
|
||||
|
||||
reduxStore.getState.mockImplementation(store.getState);
|
||||
getState.mockImplementation(store.getState);
|
||||
|
||||
await redirectUserToDefaultTeam();
|
||||
expect(getHistory().push).toHaveBeenCalledWith('/select_team');
|
||||
@ -315,7 +317,7 @@ describe('actions/global_actions', () => {
|
||||
},
|
||||
});
|
||||
|
||||
reduxStore.getState.mockImplementation(store.getState);
|
||||
getState.mockImplementation(store.getState);
|
||||
|
||||
await redirectUserToDefaultTeam();
|
||||
expect(getHistory().push).not.toHaveBeenCalled();
|
||||
@ -408,7 +410,7 @@ describe('actions/global_actions', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
reduxStore.getState.mockImplementation(store.getState);
|
||||
getState.mockImplementation(store.getState);
|
||||
LocalStorageStore.setPreviousTeamId(userId, teamId);
|
||||
LocalStorageStore.setPreviousChannelName(userId, teamId, directChannelId);
|
||||
|
||||
@ -505,7 +507,7 @@ describe('actions/global_actions', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
reduxStore.getState.mockImplementation(store.getState);
|
||||
getState.mockImplementation(store.getState);
|
||||
LocalStorageStore.setPreviousTeamId(userId, teamId);
|
||||
LocalStorageStore.setPreviousChannelName(userId, teamId, groupChannelId);
|
||||
|
||||
@ -567,7 +569,7 @@ describe('actions/global_actions', () => {
|
||||
},
|
||||
});
|
||||
|
||||
reduxStore.getState.mockImplementation(store.getState);
|
||||
getState.mockImplementation(store.getState);
|
||||
|
||||
await redirectUserToDefaultTeam();
|
||||
expect(getHistory().push).toHaveBeenCalledWith('/team1/channels/channel-in-team-1');
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Dispatch, AnyAction} from 'redux';
|
||||
|
||||
import type {Channel, ChannelMembership, ChannelMessageCount} from '@mattermost/types/channels';
|
||||
import type {Post} from '@mattermost/types/posts';
|
||||
import type {Team, TeamMembership} from '@mattermost/types/teams';
|
||||
@ -601,8 +603,8 @@ describe('Actions.User', () => {
|
||||
} as unknown as GlobalState;
|
||||
|
||||
const testStore = mockStore(state);
|
||||
store.getState.mockImplementation(testStore.getState);
|
||||
store.dispatch.mockImplementation(testStore.dispatch);
|
||||
(store.getState as jest.MockedFunction<() => GlobalState>).mockImplementation(testStore.getState);
|
||||
(store.dispatch as jest.MockedFunction<Dispatch<AnyAction>>).mockImplementation(testStore.dispatch);
|
||||
const actions = testStore.getActions();
|
||||
|
||||
await UserActions.loadProfilesForGM();
|
||||
|
@ -8,7 +8,7 @@ import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import SearchChannelWithPermissionsProvider from './search_channel_with_permissions_provider';
|
||||
|
||||
const getState = store.getState;
|
||||
const getState = jest.mocked(store.getState);
|
||||
|
||||
jest.mock('stores/redux_store', () => ({
|
||||
dispatch: jest.fn(),
|
||||
|
@ -133,6 +133,7 @@ const state: GlobalState = {
|
||||
commands: {},
|
||||
appsBotIDs: [],
|
||||
appsOAuthAppIDs: [],
|
||||
dialogTriggerId: '',
|
||||
outgoingOAuthConnections: {},
|
||||
},
|
||||
files: {
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import regeneratorRuntime from 'regenerator-runtime';
|
||||
|
||||
import type {PluginManifest} from '@mattermost/types/plugins';
|
||||
import type {PluginManifest, ClientPluginManifest} from '@mattermost/types/plugins';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {Preferences} from 'mattermost-redux/constants';
|
||||
@ -102,8 +102,8 @@ export async function initializePlugins(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(data.map((m: PluginManifest) => {
|
||||
return loadPlugin(m).catch((loadErr: Error) => {
|
||||
await Promise.all(data.map(async (m: ClientPluginManifest) => {
|
||||
return loadPlugin(m as PluginManifest).catch((loadErr: Error) => {
|
||||
console.error(loadErr.message); //eslint-disable-line no-console
|
||||
});
|
||||
}));
|
||||
@ -112,7 +112,7 @@ export async function initializePlugins(): Promise<void> {
|
||||
}
|
||||
|
||||
// getPlugins queries the server for all enabled plugins
|
||||
export function getPlugins(): ActionFuncAsync {
|
||||
export function getPlugins(): ActionFuncAsync<ClientPluginManifest[]> {
|
||||
return async (dispatch) => {
|
||||
let plugins;
|
||||
try {
|
||||
|
@ -34,8 +34,8 @@ store.subscribe(() => {
|
||||
|
||||
previousTriggerId = currentTriggerId;
|
||||
|
||||
const dialog = state.entities.integrations.dialog || {};
|
||||
if (dialog.trigger_id !== currentTriggerId) {
|
||||
const dialog = state.entities.integrations.dialog;
|
||||
if (!dialog || dialog.trigger_id !== currentTriggerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {GlobalState} from '@mattermost/types/store';
|
||||
|
||||
import {getCurrentTeamId, getTeamByName} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import localStorageStore from 'stores/local_storage_store';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
// getLastViewedChannelName combines data from the Redux store and localStorage to return the
|
||||
// previously selected channel name, returning the default channel if none exists.
|
||||
//
|
||||
|
@ -3,9 +3,13 @@
|
||||
|
||||
import baseLocalForage from 'localforage';
|
||||
import {extendPrototype} from 'localforage-observable';
|
||||
import type {Store} from 'redux';
|
||||
import type {Persistor} from 'redux-persist';
|
||||
import {persistStore, REHYDRATE} from 'redux-persist';
|
||||
import Observable from 'zen-observable';
|
||||
|
||||
import type {DeepPartial} from '@mattermost/types/utilities';
|
||||
|
||||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
import configureServiceStore from 'mattermost-redux/store';
|
||||
|
||||
@ -14,15 +18,23 @@ import {clearUserCookie} from 'actions/views/cookie';
|
||||
import appReducers from 'reducers';
|
||||
import {getBasePath} from 'selectors/general';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
function getAppReducers() {
|
||||
return require('../reducers'); // eslint-disable-line global-require
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
Observable: typeof Observable;
|
||||
}
|
||||
}
|
||||
|
||||
window.Observable = Observable;
|
||||
|
||||
const localForage = extendPrototype(baseLocalForage);
|
||||
|
||||
export default function configureStore(preloadedState, additionalReducers) {
|
||||
export default function configureStore(preloadedState?: DeepPartial<GlobalState>, additionalReducers?: Record<string, any>): Store<GlobalState> {
|
||||
const reducers = additionalReducers ? {...appReducers, ...additionalReducers} : appReducers;
|
||||
const store = configureServiceStore({
|
||||
appReducers: reducers,
|
||||
@ -31,7 +43,7 @@ export default function configureStore(preloadedState, additionalReducers) {
|
||||
});
|
||||
|
||||
localForage.ready().then(() => {
|
||||
const persistor = persistStore(store, null, () => {
|
||||
const persistor: Persistor = persistStore(store, null, () => {
|
||||
store.dispatch({
|
||||
type: General.STORE_REHYDRATION_COMPLETE,
|
||||
complete: true,
|
||||
@ -51,23 +63,23 @@ export default function configureStore(preloadedState, additionalReducers) {
|
||||
|
||||
// Rehydrate redux-persist when another tab changes localForage
|
||||
observable.subscribe({
|
||||
next: (args) => {
|
||||
if (!args.crossTabNotification) {
|
||||
next: (value) => {
|
||||
if (!value.crossTabNotification) {
|
||||
// Ignore changes made by this tab
|
||||
return;
|
||||
}
|
||||
|
||||
const keyPrefix = 'persist:';
|
||||
|
||||
if (!args.key.startsWith(keyPrefix)) {
|
||||
if (!value.key.startsWith(keyPrefix)) {
|
||||
// Ignore changes that weren't made by redux-persist
|
||||
return;
|
||||
}
|
||||
|
||||
const key = args.key.substring(keyPrefix.length);
|
||||
const newValue = JSON.parse(args.newValue);
|
||||
const key = value.key.substring(keyPrefix.length);
|
||||
const newValue = JSON.parse(value.newValue);
|
||||
|
||||
const payload = {};
|
||||
const payload: Record<string, any> = {};
|
||||
|
||||
for (const reducerKey of Object.keys(newValue)) {
|
||||
if (reducerKey === '_persist') {
|
||||
@ -110,7 +122,7 @@ export default function configureStore(preloadedState, additionalReducers) {
|
||||
});
|
||||
}
|
||||
});
|
||||
}).catch((e) => {
|
||||
}).catch((e: Error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to initialize localForage', e);
|
||||
});
|
||||
@ -121,11 +133,11 @@ export default function configureStore(preloadedState, additionalReducers) {
|
||||
/**
|
||||
* Migrates state.storage from redux-persist@4 to redux-persist@6
|
||||
*/
|
||||
function migratePersistedState(store, persistor) {
|
||||
function migratePersistedState(store: Store<GlobalState>, persistor: Persistor): void {
|
||||
const oldKeyPrefix = 'reduxPersist:storage:';
|
||||
|
||||
const restoredState = {};
|
||||
localForage.iterate((value, key) => {
|
||||
const restoredState: Record<string, string> = {};
|
||||
localForage.iterate((value: string, key: string) => {
|
||||
if (key && key.startsWith(oldKeyPrefix)) {
|
||||
restoredState[key.substring(oldKeyPrefix.length)] = value;
|
||||
}
|
||||
@ -140,7 +152,7 @@ function migratePersistedState(store, persistor) {
|
||||
|
||||
persistor.pause();
|
||||
|
||||
const persistedState = {};
|
||||
const persistedState: Record<string, any> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(restoredState)) {
|
||||
// eslint-disable-next-line no-console
|
@ -91,12 +91,12 @@ class LocalStorageStoreClass {
|
||||
this.removeItem(getPenultimateChannelNameKey(userId, teamId));
|
||||
}
|
||||
|
||||
removePreviousChannelType(userId: string, teamId: string, state = store.getStore()) {
|
||||
removePreviousChannelType(userId: string, teamId: string, state = store.getState()) {
|
||||
this.setItem(getPreviousViewedTypeKey(userId, teamId), this.getPenultimateViewedType(userId, teamId, state));
|
||||
this.removeItem(getPenultimateViewedTypeKey(userId, teamId));
|
||||
}
|
||||
|
||||
removePreviousChannel(userId: string, teamId: string, state = store.getStore()) {
|
||||
removePreviousChannel(userId: string, teamId: string, state = store.getState()) {
|
||||
this.removePreviousChannelName(userId, teamId, state);
|
||||
this.removePreviousChannelType(userId, teamId, state);
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ function replaceGlobalStore(getStore: () => any) {
|
||||
jest.spyOn(globalStore, 'dispatch').mockImplementation((...args) => getStore().dispatch(...args));
|
||||
jest.spyOn(globalStore, 'getState').mockImplementation(() => getStore().getState());
|
||||
jest.spyOn(globalStore, 'replaceReducer').mockImplementation((...args) => getStore().replaceReducer(...args));
|
||||
jest.spyOn(globalStore, '@@observable').mockImplementation((...args) => getStore()['@@observable'](...args));
|
||||
jest.spyOn(globalStore, '@@observable' as any).mockImplementation((...args: any[]) => getStore()['@@observable'](...args));
|
||||
|
||||
// This may stop working if getStore starts to return new results
|
||||
jest.spyOn(globalStore, 'subscribe').mockImplementation((...args) => getStore().subscribe(...args));
|
||||
|
@ -172,7 +172,7 @@ describe('Utils.localizeMessage', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} as any);
|
||||
});
|
||||
|
||||
test('with translations', () => {
|
||||
@ -200,7 +200,7 @@ describe('Utils.localizeMessage', () => {
|
||||
translations: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
} as any);
|
||||
});
|
||||
|
||||
test('without translations', () => {
|
||||
|
@ -1286,7 +1286,7 @@ export async function handleFormattedTextClick(e: React.MouseEvent, currentRelat
|
||||
let post = getPost(state, postId!);
|
||||
if (!post) {
|
||||
const {data: postData} = await store.dispatch(getPostAction(match.postId!));
|
||||
post = postData;
|
||||
post = postData!;
|
||||
}
|
||||
if (post) {
|
||||
isReply = Boolean(post.root_id);
|
||||
@ -1303,12 +1303,12 @@ export async function handleFormattedTextClick(e: React.MouseEvent, currentRelat
|
||||
if (!member) {
|
||||
const membership = await store.dispatch(getChannelMember(channel.id, getCurrentUserId(state)));
|
||||
if ('data' in membership) {
|
||||
member = membership.data;
|
||||
member = membership.data!;
|
||||
}
|
||||
}
|
||||
if (!member) {
|
||||
const {data} = await store.dispatch(joinPrivateChannelPrompt(team, channel.display_name, false));
|
||||
if (data.join) {
|
||||
if (data!.join) {
|
||||
let error = false;
|
||||
if (!getTeamMemberships(state)[team.id]) {
|
||||
const joinTeamResult = await store.dispatch(addUserToTeam(team.id, user.id));
|
||||
|
7
webapp/package-lock.json
generated
7
webapp/package-lock.json
generated
@ -193,6 +193,7 @@
|
||||
"@types/shallow-equals": "1.0.3",
|
||||
"@types/styled-components": "5.1.32",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/zen-observable": "0.8.7",
|
||||
"copy-webpack-plugin": "11.0.0",
|
||||
"emoji-datasource": "6.1.1",
|
||||
"emoji-datasource-apple": "6.1.1",
|
||||
@ -7310,6 +7311,12 @@
|
||||
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/zen-observable": {
|
||||
"version": "0.8.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.7.tgz",
|
||||
"integrity": "sha512-LKzNTjj+2j09wAo/vvVjzgw5qckJJzhdGgWHW7j69QIGdq/KnZrMAMIHQiWGl3Ccflh5/CudBAntTPYdprPltA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "7.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
|
||||
|
@ -134,9 +134,11 @@ export type IntegrationsState = {
|
||||
appsBotIDs: string[];
|
||||
systemCommands: IDMappedObjects<Command>;
|
||||
commands: IDMappedObjects<Command>;
|
||||
dialogTriggerId: string;
|
||||
dialog?: {
|
||||
url: string;
|
||||
dialog: Dialog;
|
||||
trigger_id: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user