Skip to content

Commit

Permalink
[expo] upfc - refactoring of test code and directory restruction
Browse files Browse the repository at this point in the history
**Summary**

we are going to put more tests in jest and this commit provides the baseline of the test code.

we also want to make a clean structure of component code base so it's documented in docs/expo/index.md.

**Test**

- yarn jest

**Issue**

- N/A
  • Loading branch information
yssk22 committed Jun 27, 2024
1 parent da28ed0 commit bd2412b
Show file tree
Hide file tree
Showing 98 changed files with 6,125 additions and 792 deletions.
23 changes: 20 additions & 3 deletions docs/expo/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,28 @@ Note that you should not include any sensitive information (such as API keys) in
```text
features/
{featurename}/
context/
components/
hooks/
tests/
internals/
```

### file structure

- Context provider and consumer hook that can be reusable in other features have to be located in context/{ContextName}.tsx
- Components that can be reusable in other features have to be located in components/{ComponentName}.tsx and encouraged to have components/{ComponentName}.test.tsx
- Hooks that can be reusable in other features have to be located in hooks/{hookName}.tsx and encouraged to have components/{hookName}.test.tsx
- internal components and hooks used in the same features can be located in `internals/`

### nameing convenstions

- A class name or component name should have `{FeatureName}{ComponentName}` format.
- An Error class should be exported as `Err{FeatureName}{ErrorName}` and defined in `errors.ts`.
- if a non component type is used in multiple files within the same feature, it should be defined in `types.ts` file.
- if a constant is used in multiple files, it should be defined in `constants.ts` file.

Note that `features/common` is a common feature that contains components, hooks, and contexts that can be used in multiple features so `{FeatureName}` prefix should be omitted.

### `foundation`

`foundation` is a directory that contains TypeScript foundations. These are utilitise for builtin types such as `Date`, `Error`, `Function`, ...etc. Each module here should not depends on any other third party libraries includig `react` or `expo`.
Expand All @@ -49,6 +66,6 @@ features/

- Do not use `../` or `./` in module imports. Use `@hpapp/` instead.

## See also
## See also

- [typedoc](./typedoc/index.html)
- [typedoc](./typedoc/index.html)
46 changes: 46 additions & 0 deletions docs/expo/upfc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# UPFC

UPFC is a feature to manage fan club data at `up-fc.jp`. It is built on top of clien-side scraping and GraphQL API.

## How it works

We use the normal [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) API to make a HTTP call and [`jsdom-jscore-rn`](https://github.com/iamcco/jsdom-jscore-rn)
to parse the returned HTML contents to extract the data we need.

This describes how `useUPFC()` hook works.

### Authentication

1. Issue `GET https://www.up-fc.jp/helloproject/fanclub_Login.php` so that up-fc.jp issues a session cookie.
2. Issue `POST https://www.up-fc.jp/helloproject/fanclub_Login.php` with the following form data.
- `User_No` - fan club user number
- `User_LoginPassword` - fan club password
- `pp` - this has to be 'OK';
- `@Control_Name@` - this has to be '認証';
3. If the response HTML contains `http-equiv` in their meta tag, which navigate users to redirect to `index.php`, it means the authentication is successful. If not, authentication fails.

### Parse event applications and their Tickets

There are 3 paegs that has to be scraped to get

- https://www.up-fc.jp/helloproject/event_Event_SetList.php returns all available event applications for nomal memberships.
- https://www.up-fc.jp/helloproject/fanticket_DM_List.php returns all available event applications for executive memberships.
- https://www.up-fc.jp/helloproject/mypage02.php returns applications with their statuses user has already applied.

So at first we fetch 3 html pages in parallel, then scrape each to build `UPFCEventApplicationTickets[]`

### Once event applications are built

- We send the list to GraphQL API to store these records. records at `up-fc.jp` will expire after the event is over but we want users to track these applications and make better decision on where/when to go. As a return of sending the list to our service, we'll provide the anonymous stats about applications.
- The applicatio records are also converted into Calender events and reminders.

## Security Considerations

- A credential at `up-fc.jp` is not very strong and there is no way for users to update the credentials on the web. So we **must not send these credentials to our service**. To avoid duplication of application records on the server side, the client sends the **SHA256 hash of username** so that we can identify the user without knowing the actual username.
- For usability reasons, a credential is stored in SecureStorage on the devices.

## Development and Test

We cannot create a test account for up-fc.jp so we use someones' account to fetch the necessary HTML contents, store them in test fixtures and reuse it for testing. @hpapp/features/upfc/internals/scraper/testdata is a data captured by the developer in 2020 and 2021 where executive membership was active.

We also have dummy data in DemoScraper implementation to test look&feel.
6 changes: 2 additions & 4 deletions expo/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import Root from './features/root';
import screens from './generated/Screens';
import RootApp from './features/root/components/RootApp';

export default function App() {
// for production build
return <Root screens={screens} />;
return <RootApp />;
}
8 changes: 7 additions & 1 deletion expo/assets/translations.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"something went wrong.": {
"ja": "エラーが発生しました"
"ja": "エラーが発生しました"
},
"Update is available. Tap to install.": {
"ja": "アップデートが利用可能です。タップしてインストール。"
Expand Down Expand Up @@ -110,5 +110,11 @@
},
"Logout": {
"ja": "ログアウト"
},
"Saved Successfully!": {
"ja": "保存しました!"
},
"authenticatoin failed.": {
"ja": "認証に失敗しました。"
}
}
15 changes: 13 additions & 2 deletions expo/features/appconfig/AppConfigModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ type AppConfigModalProps = { isVisible: boolean; onClose: () => void };
* @param {boolean} visible - Indicates whether the modal is visible.
* @param {() => void} onClose - Callback function to close the modal.
*/
export default function AppConfigMoal({ isVisible, onClose }: AppConfigModalProps) {
export default function AppConfigModal({ isVisible, onClose }: AppConfigModalProps) {
const [appConfig, setAppConfig] = useAppConfigForEdit();
const [useLocalAppConfig, setUseLocalAppConfig] = useState(appConfig?.useLocalAppConfig ?? false);
const [graphQLEndpoint, setGraphQLEndpoint] = useState(appConfig?.graphQLEndpoint ?? '');
const [useLocalAuth, setUseLocalAuth] = useState(appConfig?.useLocalAuth ?? false);
const [useUPFCDemoScraper, setUseUPFCDemoScraper] = useState(appConfig?.useUPFCDemoScraper ?? false);
const onSave = () => {
setAppConfig({
useLocalAppConfig,
graphQLEndpoint,
useLocalAuth
useLocalAuth,
useUPFCDemoScraper
});
onClose();
};
Expand Down Expand Up @@ -65,6 +67,15 @@ export default function AppConfigMoal({ isVisible, onClose }: AppConfigModalProp
}}
/>
</View>
<View style={[styles.inputGroup, styles.switchContainer]}>
<Text style={styles.label}>Use UPFC Demo Scraper</Text>
<Switch
value={useUPFCDemoScraper}
onValueChange={() => {
setUseUPFCDemoScraper(!useUPFCDemoScraper);
}}
/>
</View>
</View>
<View style={styles.buttonGroup}>
<Button type="outline" style={styles.button} onPress={onClose}>
Expand Down
4 changes: 3 additions & 1 deletion expo/features/appconfig/useAppConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ type AppConfig = {
readonly useLocalAppConfig: boolean;
readonly graphQLEndpoint: string;
readonly useLocalAuth: boolean;
readonly useUPFCDemoScraper: boolean;
};

const DefaultAppConfig: AppConfig = {
useLocalAppConfig: false,
graphQLEndpoint: Constants.expoConfig?.extra!.hpapp!.graphQLEndpoint!,
useLocalAuth: Constants.expoConfig?.extra!.hpapp!.useLocalAuth!
useLocalAuth: Constants.expoConfig?.extra!.hpapp!.useLocalAuth!,
useUPFCDemoScraper: true
};

const AppConfigSettings = SettingsStore.register<AppConfig>('hpapp.appconfig', new AsyncStorage(), {
Expand Down
2 changes: 1 addition & 1 deletion expo/features/artist/ArtistCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import MemberIcon from '@hpapp/features/artist/MemberIcon';
import Text from '@hpapp/features/common/components/Text';
import { Spacing } from '@hpapp/features/common/constants';
import { HPArtist } from '@hpapp/features/root/protected/context/helloproject';
import { HPArtist } from '@hpapp/features/root/internals/protected/context/helloproject';
import { Card } from '@rneui/themed';
import { useCallback, useMemo, useState } from 'react';
import { View, StyleSheet, LayoutChangeEvent } from 'react-native';
Expand Down
2 changes: 1 addition & 1 deletion expo/features/artist/ByAgeView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import GroupedViewListItem from '@hpapp/features/artist/GroupedViewListItem';
import { HPMember, useHelloProject } from '@hpapp/features/root/protected/context';
import { HPMember, useHelloProject } from '@hpapp/features/root/internals/protected/context';
import { getAcademicYear } from '@hpapp/foundation/date';
import { useMemo } from 'react';
import { FlatList } from 'react-native';
Expand Down
2 changes: 1 addition & 1 deletion expo/features/artist/ByGroupView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ArtistCard from '@hpapp/features/artist/ArtistCard';
import { useHelloProject } from '@hpapp/features/root/protected/context';
import { useHelloProject } from '@hpapp/features/root/internals/protected/context';
import { FlatList } from 'react-native';

export default function ByGroupView() {
Expand Down
2 changes: 1 addition & 1 deletion expo/features/artist/BySortView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import HPSortResultListView from '@hpapp/features/artist/sort/HPSortResultListView';
import { useMe } from '@hpapp/features/root/protected/context';
import { useMe } from '@hpapp/features/root/internals/protected/context';
import { t } from '@hpapp/system/i18n';
import { Text } from '@rneui/themed';
import { useMemo } from 'react';
Expand Down
4 changes: 2 additions & 2 deletions expo/features/artist/FollowIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HPMember, useHelloProject, useMe } from '@hpapp/features/root/protected/context';
import { HPFollowType } from '@hpapp/features/root/protected/context/me';
import { HPMember, useHelloProject, useMe } from '@hpapp/features/root/internals/protected/context';
import { HPFollowType } from '@hpapp/features/root/internals/protected/context/me';
import { ColorScheme, useColor } from '@hpapp/features/settings/context/theme';
import { Icon } from '@rneui/themed';

Expand Down
2 changes: 1 addition & 1 deletion expo/features/artist/GroupedViewListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import MemberIcon from '@hpapp/features/artist/MemberIcon';
import Text from '@hpapp/features/common/components/Text';
import { FontSize, MemberIconSize, Spacing } from '@hpapp/features/common/constants';
import { HPMember } from '@hpapp/features/root/protected/context';
import { HPMember } from '@hpapp/features/root/internals/protected/context';
import { toDateString, getAge } from '@hpapp/foundation/date';
import { t } from '@hpapp/system/i18n';
import { Divider, ListItem } from '@rneui/themed';
Expand Down
2 changes: 1 addition & 1 deletion expo/features/artist/MemberIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ExternalImage from '@hpapp/features/common/components/image';
import { MemberIconSize } from '@hpapp/features/common/constants';
import { HPMember, useHelloProject } from '@hpapp/features/root/protected/context';
import { HPMember, useHelloProject } from '@hpapp/features/root/internals/protected/context';
import { View } from 'react-native';

export default function MemberIcon({
Expand Down
2 changes: 1 addition & 1 deletion expo/features/artist/sort/HPSortResultListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import MemberIcon from '@hpapp/features/artist/MemberIcon';
import HPSortResultRankDiffIcon from '@hpapp/features/artist/sort/HPSortResultRankDiffIcon';
import Text from '@hpapp/features/common/components/Text';
import { Spacing, FontSize, MemberIconSize, IconSize } from '@hpapp/features/common/constants';
import { useHelloProject } from '@hpapp/features/root/protected/context';
import { useHelloProject } from '@hpapp/features/root/internals/protected/context';
import { Divider } from '@rneui/themed';
import { StyleSheet, View } from 'react-native';

Expand Down
2 changes: 1 addition & 1 deletion expo/features/artist/sort/HPSortResultListTopItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import MemberIcon from '@hpapp/features/artist/MemberIcon';
import HPSortResultRankDiffIcon from '@hpapp/features/artist/sort/HPSortResultRankDiffIcon';
import Text from '@hpapp/features/common/components/Text';
import { FontSize, IconSize, MemberIconSize, Spacing } from '@hpapp/features/common/constants';
import { useHelloProject } from '@hpapp/features/root/protected/context';
import { useHelloProject } from '@hpapp/features/root/internals/protected/context';
import { Icon } from '@rneui/themed';
import { View, StyleSheet } from 'react-native';

Expand Down
Loading

0 comments on commit bd2412b

Please sign in to comment.