Migrate @woocommerce/data navigation store to TS (#34239)

* Migrate woo data navigation store to TS

* Add changelog

* Fix action type

* Update packages/js/data/src/navigation/reducer.ts

Co-authored-by: Ilyas Foo <foo.ilyas@gmail.com>

* Add a doc comment to MenuItem type

Co-authored-by: Ilyas Foo <foo.ilyas@gmail.com>
This commit is contained in:
Chi-Hsuan Huang 2022-08-17 14:32:38 +08:00 committed by GitHub
parent a6d656f363
commit 02b47a67db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 174 additions and 71 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Migrate navigation store to TS

View File

@ -11,6 +11,6 @@ const TYPES = {
REMOVE_FAVORITE_FAILURE: 'REMOVE_FAVORITE_FAILURE',
REMOVE_FAVORITE_REQUEST: 'REMOVE_FAVORITE_REQUEST',
REMOVE_FAVORITE_SUCCESS: 'REMOVE_FAVORITE_SUCCESS',
};
} as const;
export default TYPES;

View File

@ -9,50 +9,51 @@ import { getPersistedQuery } from '@woocommerce/navigation';
*/
import TYPES from './action-types';
import { WC_ADMIN_NAMESPACE } from '../constants';
import { MenuItem } from './types';
export function setMenuItems( menuItems ) {
export function setMenuItems( menuItems: MenuItem[] ) {
return {
type: TYPES.SET_MENU_ITEMS,
menuItems,
};
}
export function addMenuItems( menuItems ) {
export function addMenuItems( menuItems: MenuItem[] ) {
return {
type: TYPES.ADD_MENU_ITEMS,
menuItems,
};
}
export function getFavoritesFailure( error ) {
export function getFavoritesFailure( error: unknown ) {
return {
type: TYPES.GET_FAVORITES_FAILURE,
error,
};
}
export function getFavoritesRequest( favorites ) {
export function getFavoritesRequest( favorites?: string[] ) {
return {
type: TYPES.GET_FAVORITES_REQUEST,
favorites,
};
}
export function getFavoritesSuccess( favorites ) {
export function getFavoritesSuccess( favorites: string[] ) {
return {
type: TYPES.GET_FAVORITES_SUCCESS,
favorites,
};
}
export function addFavoriteRequest( favorite ) {
export function addFavoriteRequest( favorite: string ) {
return {
type: TYPES.ADD_FAVORITE_REQUEST,
favorite,
};
}
export function addFavoriteFailure( favorite, error ) {
export function addFavoriteFailure( favorite: string, error: unknown ) {
return {
type: TYPES.ADD_FAVORITE_FAILURE,
favorite,
@ -60,21 +61,21 @@ export function addFavoriteFailure( favorite, error ) {
};
}
export function addFavoriteSuccess( favorite ) {
export function addFavoriteSuccess( favorite: string ) {
return {
type: TYPES.ADD_FAVORITE_SUCCESS,
favorite,
};
}
export function removeFavoriteRequest( favorite ) {
export function removeFavoriteRequest( favorite: string ) {
return {
type: TYPES.REMOVE_FAVORITE_REQUEST,
favorite,
};
}
export function removeFavoriteFailure( favorite, error ) {
export function removeFavoriteFailure( favorite: string, error: unknown ) {
return {
type: TYPES.REMOVE_FAVORITE_FAILURE,
favorite,
@ -82,18 +83,13 @@ export function removeFavoriteFailure( favorite, error ) {
};
}
export function removeFavoriteSuccess( favorite, error ) {
export function removeFavoriteSuccess( favorite: string ) {
return {
type: TYPES.REMOVE_FAVORITE_SUCCESS,
favorite,
error,
};
}
export function* onLoad() {
yield onHistoryChange();
}
export function* onHistoryChange() {
const persistedQuery = getPersistedQuery();
@ -107,11 +103,15 @@ export function* onHistoryChange() {
};
}
export function* addFavorite( favorite ) {
export function* onLoad() {
yield onHistoryChange();
}
export function* addFavorite( favorite: string ) {
yield addFavoriteRequest( favorite );
try {
const results = yield apiFetch( {
const results: string[] = yield apiFetch( {
path: `${ WC_ADMIN_NAMESPACE }/navigation/favorites/me`,
method: 'POST',
data: {
@ -131,11 +131,11 @@ export function* addFavorite( favorite ) {
}
}
export function* removeFavorite( favorite ) {
export function* removeFavorite( favorite: string ) {
yield removeFavoriteRequest( favorite );
try {
const results = yield apiFetch( {
const results: string[] = yield apiFetch( {
path: `${ WC_ADMIN_NAMESPACE }/navigation/favorites/me`,
method: 'DELETE',
data: {
@ -154,3 +154,21 @@ export function* removeFavorite( favorite ) {
throw new Error();
}
}
export type Action = ReturnType<
| typeof setMenuItems
| typeof addMenuItems
| typeof getFavoritesFailure
| typeof getFavoritesRequest
| typeof getFavoritesSuccess
| typeof addFavoriteRequest
| typeof addFavoriteFailure
| typeof addFavoriteSuccess
| typeof removeFavoriteRequest
| typeof removeFavoriteFailure
| typeof removeFavoriteSuccess
| ( () => {
type: typeof TYPES.ON_HISTORY_CHANGE;
persistedQuery: ReturnType< typeof getPersistedQuery >;
} )
>;

View File

@ -1 +1 @@
export const STORE_NAME = 'woocommerce-navigation';
export const STORE_NAME = 'woocommerce-navigation' as const;

View File

@ -1,26 +0,0 @@
/**
* External dependencies
*/
import { controls } from '@wordpress/data-controls';
import { registerStore } from '@wordpress/data';
/**
* Internal dependencies
*/
import { STORE_NAME } from './constants';
import * as selectors from './selectors';
import * as actions from './actions';
import reducer from './reducer';
import * as resolvers from './resolvers';
import initDispatchers from './dispatchers';
registerStore( STORE_NAME, {
reducer,
actions,
controls,
resolvers,
selectors,
} );
initDispatchers();
export const NAVIGATION_STORE_NAME = STORE_NAME;

View File

@ -0,0 +1,38 @@
/**
* External dependencies
*/
import { registerStore } from '@wordpress/data';
import { controls } from '@wordpress/data-controls';
import { SelectFromMap, DispatchFromMap } from '@automattic/data-stores';
import { Reducer, AnyAction } from 'redux';
/**
* Internal dependencies
*/
import { STORE_NAME } from './constants';
import * as selectors from './selectors';
import * as actions from './actions';
import reducer, { State } from './reducer';
import * as resolvers from './resolvers';
import initDispatchers from './dispatchers';
import { WPDataActions, WPDataSelectors } from '../types';
registerStore< State >( STORE_NAME, {
reducer: reducer as Reducer< State, AnyAction >,
actions,
controls,
resolvers,
selectors,
} );
initDispatchers();
export const NAVIGATION_STORE_NAME = STORE_NAME;
declare module '@wordpress/data' {
function dispatch(
key: typeof STORE_NAME
): DispatchFromMap< typeof actions & WPDataActions >;
function select(
key: typeof STORE_NAME
): SelectFromMap< typeof selectors > & WPDataSelectors;
}

View File

@ -1,9 +1,16 @@
/**
* External dependencies
*/
import type { Reducer } from 'redux';
/**
* Internal dependencies
*/
import TYPES from './action-types';
import { Action } from './actions';
import { NavigationState } from './types';
const reducer = (
const reducer: Reducer< NavigationState, Action > = (
state = {
error: null,
menuItems: [],
@ -11,25 +18,25 @@ const reducer = (
requesting: {},
persistedQuery: {},
},
{ type, error, favorite, favorites, menuItems, persistedQuery }
action
) => {
switch ( type ) {
switch ( action.type ) {
case TYPES.SET_MENU_ITEMS:
state = {
...state,
menuItems,
menuItems: action.menuItems,
};
break;
case TYPES.ADD_MENU_ITEMS:
state = {
...state,
menuItems: [ ...state.menuItems, ...menuItems ],
menuItems: [ ...state.menuItems, ...action.menuItems ],
};
break;
case TYPES.ON_HISTORY_CHANGE:
state = {
...state,
persistedQuery,
persistedQuery: action.persistedQuery,
};
break;
case TYPES.GET_FAVORITES_FAILURE:
@ -53,7 +60,7 @@ const reducer = (
case TYPES.GET_FAVORITES_SUCCESS:
state = {
...state,
favorites,
favorites: action.favorites,
requesting: {
...state.requesting,
getFavorites: false,
@ -63,7 +70,7 @@ const reducer = (
case TYPES.ADD_FAVORITE_FAILURE:
state = {
...state,
error,
error: action.error,
requesting: {
...state.requesting,
addFavorite: false,
@ -80,15 +87,15 @@ const reducer = (
};
break;
case TYPES.ADD_FAVORITE_SUCCESS:
const newFavorites = ! state.favorites.includes( favorite )
? [ ...state.favorites, favorite ]
const newFavorites = ! state.favorites.includes( action.favorite )
? [ ...state.favorites, action.favorite ]
: state.favorites;
state = {
...state,
favorites: newFavorites,
menuItems: state.menuItems.map( ( item ) => {
if ( item.id === favorite ) {
if ( item.id === action.favorite ) {
return {
...item,
menuId: 'favorites',
@ -107,7 +114,7 @@ const reducer = (
...state,
requesting: {
...state.requesting,
error,
error: action.error,
removeFavorite: false,
},
};
@ -123,14 +130,14 @@ const reducer = (
break;
case TYPES.REMOVE_FAVORITE_SUCCESS:
const filteredFavorites = state.favorites.filter(
( f ) => f !== favorite
( f ) => f !== action.favorite
);
state = {
...state,
favorites: filteredFavorites,
menuItems: state.menuItems.map( ( item ) => {
if ( item.id === favorite ) {
if ( item.id === action.favorite ) {
return {
...item,
menuId: 'plugins',
@ -148,4 +155,5 @@ const reducer = (
return state;
};
export type State = ReturnType< typeof reducer >;
export default reducer;

View File

@ -17,7 +17,7 @@ export function* getFavorites() {
yield getFavoritesRequest();
try {
const results = yield apiFetch( {
const results: string[] = yield apiFetch( {
path: `${ WC_ADMIN_NAMESPACE }/navigation/favorites/me`,
} );

View File

@ -3,9 +3,14 @@
*/
import { applyFilters } from '@wordpress/hooks';
const MENU_ITEMS_HOOK = 'woocommerce_navigation_menu_items';
/**
* Internal dependencies
*/
import { NavigationState } from './types';
export const getMenuItems = ( state ) => {
const MENU_ITEMS_HOOK = 'woocommerce_navigation_menu_items' as const;
export const getMenuItems = ( state: NavigationState ) => {
/**
* Navigation Menu Items.
*
@ -15,14 +20,17 @@ export const getMenuItems = ( state ) => {
return applyFilters( MENU_ITEMS_HOOK, state.menuItems );
};
export const getFavorites = ( state ) => {
export const getFavorites = ( state: NavigationState ) => {
return state.favorites || [];
};
export const isNavigationRequesting = ( state, selector ) => {
export const isNavigationRequesting = (
state: NavigationState,
selector: string
) => {
return state.requesting[ selector ] || false;
};
export const getPersistedQuery = ( state ) => {
export const getPersistedQuery = ( state: NavigationState ) => {
return state.persistedQuery || {};
};

View File

@ -14,6 +14,7 @@ const defaultState = {
describe( 'navigation reducer', () => {
it( 'should return a default state', () => {
// @ts-expect-error -- we're testing the reducer's default state.
const state = reducer( undefined, {} );
expect( state ).toEqual( defaultState );
expect( state ).not.toBe( defaultState );
@ -27,16 +28,25 @@ describe( 'navigation reducer', () => {
id: 'menu-item-1',
title: 'Menu Item 1',
menuId: 'primary',
url: 'https://example.com/menu-item-1',
migrate: true,
order: 1,
},
{
id: 'menu-item-2',
title: 'Menu Item 2',
menuId: 'primary',
url: 'https://example.com/menu-item-2',
migrate: true,
order: 2,
},
{
id: 'menu-item-3',
title: 'Menu Item 3',
menuId: 'secondary',
url: 'https://example.com/menu-item-3',
migrate: true,
order: 3,
},
],
} );
@ -55,8 +65,15 @@ describe( 'navigation reducer', () => {
id: 'menu-item-1',
title: 'Menu Item 1',
menuId: 'primary',
url: 'https://example.com/menu-item-1',
migrate: true,
order: 1,
},
],
error: null,
favorites: [],
requesting: {},
persistedQuery: {},
},
{
type: TYPES.ADD_MENU_ITEMS,
@ -65,6 +82,9 @@ describe( 'navigation reducer', () => {
id: 'menu-item-2',
title: 'Menu Item 2',
menuId: 'primary',
url: 'https://example.com/menu-item-2',
migrate: true,
order: 2,
},
],
}

View File

@ -0,0 +1,30 @@
/**
* External dependencies
*/
import { getPersistedQuery } from '@woocommerce/navigation';
// https://github.com/woocommerce/woocommerce/blob/ecec9eaa76cb7b2e36f79175837b71f4f64996b1/plugins/woocommerce/src/Admin/Features/Navigation/Menu.php
export type MenuItem = {
id: string;
title: string;
url: string;
order: number;
migrate: boolean;
menuId: string;
isCategory?: boolean;
badge?: number;
backButtonLabel?: string;
parent?: string;
capability?: string;
matchExpression?: string;
};
export type NavigationState = {
error: null | unknown;
menuItems: MenuItem[];
favorites: string[];
requesting: {
[ key: string ]: boolean | unknown;
};
persistedQuery: ReturnType< typeof getPersistedQuery >;
};

View File

@ -9,17 +9,20 @@ import { createElement, useRef } from '@wordpress/element';
* Internal dependencies
*/
import { STORE_NAME } from './constants';
import { MenuItem } from './types';
/**
* Higher-order component used to hydrate navigation data.
*
* @param {Object} data Data object with menu items and site information.
* @param {Object} data Data object with menu items and site information.
* @param {MenuItem[]} data.menuItems Menu items to hydrate.
*/
export const withNavigationHydration = ( data ) =>
createHigherOrderComponent(
export const withNavigationHydration = ( data: { menuItems: MenuItem[] } ) =>
createHigherOrderComponent< Record< string, unknown > >(
( OriginalComponent ) => ( props ) => {
const dataRef = useRef( data );
// @ts-expect-error // @ts-expect-error registry is not defined in the wp.data typings
useSelect( ( select, registry ) => {
if ( ! dataRef.current ) {
return;