Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/__tests__/lib/exampleData.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,15 @@ export const realm = 'https://zulip.example.org';

export const zulipVersion = new ZulipVersion('2.1.0-234-g7c3acf4');

export const zulipFeatureLevel = 1;

export const makeAccount = (
args: {
user?: User,
email?: string,
realm?: string,
apiKey?: string,
zulipFeatureLevel?: number | null,
zulipVersion?: ZulipVersion | null,
ackedPushToken?: string | null,
} = {},
Expand All @@ -142,13 +145,15 @@ export const makeAccount = (
email = user.email,
realm: realmInner = realm,
apiKey = randString() + randString(),
zulipFeatureLevel: zulipFeatureLevelInner = zulipFeatureLevel,
zulipVersion: zulipVersionInner = zulipVersion,
ackedPushToken = null,
} = args;
return deepFreeze({
realm: realmInner,
email,
apiKey,
zulipFeatureLevel: zulipFeatureLevelInner,
zulipVersion: zulipVersionInner,
ackedPushToken,
});
Expand Down Expand Up @@ -473,6 +478,7 @@ export const action = deepFreeze({
realm_uri: selfAccount.realm,
realm_video_chat_provider: 1,
realm_waiting_period_threshold: 3,
zulip_feature_level: 1,
realm_emoji: {},
realm_filters: [],
avatar_source: 'G',
Expand Down
126 changes: 78 additions & 48 deletions src/account/__tests__/accountsReducer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,58 +16,77 @@ import * as eg from '../../__tests__/lib/exampleData';

describe('accountsReducer', () => {
describe('REALM_ADD', () => {
const accountWithZulipVersion = eg.makeAccount({
apiKey: '',
realm: 'https://realm.one.org',
});
const accountWithoutZulipVersion = eg.makeAccount({
apiKey: '',
zulipVersion: null,
realm: 'https://realm.two.org',
});
const prevState = deepFreeze([accountWithZulipVersion, accountWithoutZulipVersion]);

test('if no account with this realm exists, prepend new one, with empty email/apiKey', () => {
const newAccount = eg.makeAccount({
email: '',
apiKey: '',
realm: 'https://new.realm.org',
});

const action = deepFreeze({
describe('on list of identities', () => {
const account1 = eg.makeAccount({ realm: 'https://realm.one.org', apiKey: '' });
const account2 = eg.makeAccount({ realm: 'https://realm.two.org', apiKey: '' });
const prevState = deepFreeze([account1, account2]);
const baseAction = deepFreeze({
type: REALM_ADD,
realm: newAccount.realm,
zulipFeatureLevel: eg.zulipFeatureLevel,
zulipVersion: eg.zulipVersion,
});

const expectedState = [newAccount, accountWithZulipVersion, accountWithoutZulipVersion];

const newState = accountsReducer(prevState, action);

expect(newState).toEqual(expectedState);
expect(newState).not.toBe(prevState);
});
test('if no account with this realm exists, prepend new one, with empty email/apiKey', () => {
const newRealm = 'https://new.realm.org';
const action = deepFreeze({ ...baseAction, realm: newRealm });
expect(accountsReducer(prevState, action)).toEqual([
eg.makeAccount({ realm: newRealm, email: '', apiKey: '' }),
account1,
account2,
]);
});

test('if account with this realm exists, move to front of list', () => {
const newAccount = eg.makeAccount({
email: accountWithoutZulipVersion.email,
realm: accountWithoutZulipVersion.realm,
apiKey: '',
zulipVersion: eg.zulipVersion,
test('if account with this realm exists, move to front of list', () => {
const action = deepFreeze({ ...baseAction, realm: account2.realm });
expect(accountsReducer(prevState, action)).toEqual([account2, account1]);
});
});

const action = deepFreeze({
describe('if an account with this realm exists', () => {
const existingAccountBase = eg.makeAccount({});
const baseAction = deepFreeze({
type: REALM_ADD,
realm: newAccount.realm,
realm: existingAccountBase.realm,
zulipFeatureLevel: eg.zulipFeatureLevel,
zulipVersion: eg.zulipVersion,
});

const expectedState = [newAccount, accountWithZulipVersion];

const newState = accountsReducer(prevState, action);
describe('update its zulipVersion', () => {
const newZulipVersion = new ZulipVersion('4.0.0');
const action = deepFreeze({ ...baseAction, zulipVersion: newZulipVersion });

test('when its zulipVersion started out non-null', () => {
expect(
accountsReducer(
[{ ...existingAccountBase, zulipVersion: new ZulipVersion('3.0.0') }],
action,
),
).toEqual([{ ...existingAccountBase, zulipVersion: newZulipVersion }]);
});

test('when its zulipVersion started out null', () => {
expect(accountsReducer([{ ...existingAccountBase, zulipVersion: null }], action)).toEqual(
[{ ...existingAccountBase, zulipVersion: newZulipVersion }],
);
});
});

expect(newState).toEqual(expectedState);
expect(newState).not.toBe(prevState);
describe('update its zulipFeatureLevel', () => {
const newZulipFeatureLevel = 6;
const action = deepFreeze({ ...baseAction, zulipFeatureLevel: newZulipFeatureLevel });

test('when its zulipFeatureLevel started out non-null', () => {
expect(
accountsReducer([{ ...existingAccountBase, zulipFeatureLevel: 5 }], action),
).toEqual([{ ...existingAccountBase, zulipFeatureLevel: newZulipFeatureLevel }]);
});

test('when its zulipVersion started out null', () => {
expect(
accountsReducer([{ ...existingAccountBase, zulipFeatureLevel: null }], action),
).toEqual([{ ...existingAccountBase, zulipFeatureLevel: newZulipFeatureLevel }]);
});
});
});
});

Expand All @@ -77,16 +96,26 @@ describe('accountsReducer', () => {
const account3 = eg.makeAccount();

test('records zulipVersion on active account', () => {
const prevState = deepFreeze([account1, account2, account3]);
const newZulipVersion = new ZulipVersion('2.0.0');
const action = deepFreeze({ ...eg.action.realm_init, zulipVersion: newZulipVersion });

const expectedState = [{ ...account1, zulipVersion: newZulipVersion }, account2, account3];

const newState = accountsReducer(prevState, action);
expect(
accountsReducer(
deepFreeze([account1, account2, account3]),
deepFreeze({ ...eg.action.realm_init, zulipVersion: newZulipVersion }),
),
).toEqual([{ ...account1, zulipVersion: newZulipVersion }, account2, account3]);
});

expect(newState).toEqual(expectedState);
expect(newState).not.toBe(prevState);
test('records zulipFeatureLevel on active account', () => {
const newZulipFeatureLevel = 6;
expect(
accountsReducer(
deepFreeze([account1, account2, account3]),
deepFreeze({
...eg.action.realm_init,
data: { ...eg.action.realm_init.data, zulip_feature_level: newZulipFeatureLevel },
}),
),
).toEqual([{ ...account1, zulipFeatureLevel: newZulipFeatureLevel }, account2, account3]);
});
});

Expand Down Expand Up @@ -154,6 +183,7 @@ describe('accountsReducer', () => {
email: '[email protected]',
realm: 'https://new.realm.org',
zulipVersion: null,
zulipFeatureLevel: null,
});

const action = deepFreeze({
Expand Down
7 changes: 6 additions & 1 deletion src/account/accountActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ export const switchAccount = (index: number): Action => ({
index,
});

export const realmAdd = (realm: string, zulipVersion: ZulipVersion): Action => ({
export const realmAdd = (
realm: string,
zulipFeatureLevel: number,
zulipVersion: ZulipVersion,
): Action => ({
type: REALM_ADD,
realm,
zulipFeatureLevel,
zulipVersion,
});

Expand Down
8 changes: 7 additions & 1 deletion src/account/accountsReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const realmAdd = (state, action) => {
if (accountIndex !== -1) {
const newAccount = {
...state[accountIndex],
zulipFeatureLevel: action.zulipFeatureLevel,
zulipVersion: action.zulipVersion,
};
return [newAccount, ...state.slice(0, accountIndex), ...state.slice(accountIndex + 1)];
Expand All @@ -32,6 +33,7 @@ const realmAdd = (state, action) => {
apiKey: '',
email: '',
ackedPushToken: null,
zulipFeatureLevel: action.zulipFeatureLevel,
zulipVersion: action.zulipVersion,
},
...state,
Expand All @@ -41,6 +43,7 @@ const realmAdd = (state, action) => {
const realmInit = (state, action) => [
{
...state[0],
zulipFeatureLevel: action.data.zulip_feature_level ?? 0,
zulipVersion: action.zulipVersion,
},
...state.slice(1),
Expand All @@ -65,7 +68,10 @@ const loginSuccess = (state, action) => {
const { realm, email, apiKey } = action;
const accountIndex = findAccount(state, { realm, email });
if (accountIndex === -1) {
return [{ realm, email, apiKey, ackedPushToken: null, zulipVersion: null }, ...state];
return [
{ realm, email, apiKey, ackedPushToken: null, zulipVersion: null, zulipFeatureLevel: null },
...state,
];
}
return [
{ ...state[accountIndex], email, apiKey, ackedPushToken: null },
Expand Down
1 change: 1 addition & 0 deletions src/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ type AccountSwitchAction = {|
type RealmAddAction = {|
type: typeof REALM_ADD,
realm: string,
zulipFeatureLevel: number,
zulipVersion: ZulipVersion,
|};

Expand Down
7 changes: 7 additions & 0 deletions src/api/initialDataTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ export type InitialDataRealm = {|
realm_uri: string,
realm_video_chat_provider: number,
realm_waiting_period_threshold: number,

/**
* Added in server version 2.2, feature level 1.
* Same meaning as in the server_settings response:
* https://zulipchat.com/api/server-settings
*/
zulip_feature_level?: number,
|};

export type InitialDataRealmEmoji = {|
Expand Down
4 changes: 4 additions & 0 deletions src/api/settings/getServerSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export type ApiResponseServerSettings = {|
realm_uri: string,
require_email_format_usernames: boolean,
zulip_version: string,

// zulip_feature_level added for server v2.2, feature level 1
// See https://zulipchat.com/api/server-settings
zulip_feature_level?: number,
|};

/** See https://zulip.com/api/server-settings */
Expand Down
21 changes: 15 additions & 6 deletions src/boot/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ const migrations: { [string]: (GlobalState) => GlobalState } = {
accounts: state.accounts.map(a => ({
...a,
// but in the case of `ackedPushToken` let's be a bit more precise,
// and avoid clobbering it if present.
// and avoid clobbering it if present. (Don't copy this pattern for a
// normal migration; this uncertainty is specific to recovering from #3553.)
ackedPushToken: a.ackedPushToken !== undefined ? a.ackedPushToken : null,
})),
}),
Expand Down Expand Up @@ -159,22 +160,30 @@ const migrations: { [string]: (GlobalState) => GlobalState } = {
})),
}),

// Accounts.zulipVersion is now string | null
// Add Accounts.zulipVersion, as string | null.
'12': state => ({
...state,
accounts: state.accounts.map(a => ({
...a,
zulipVersion: a.zulipVersion !== undefined ? a.zulipVersion : null,
zulipVersion: null,
})),
}),

// Accounts.zulipVersion is now ZulipVersion | null
// Convert Accounts.zulipVersion from `string | null` to `ZulipVersion | null`.
'13': state => ({
...state,
accounts: state.accounts.map(a => ({
...a,
zulipVersion:
typeof a.zulipVersion === 'string' ? new ZulipVersion(a.zulipVersion) : a.zulipVersion,
zulipVersion: typeof a.zulipVersion === 'string' ? new ZulipVersion(a.zulipVersion) : null,
})),
}),

// Add Accounts.zulipFeatureLevel, as number | null.
'14': state => ({
...state,
accounts: state.accounts.map(a => ({
...a,
zulipFeatureLevel: null,
})),
}),

Expand Down
8 changes: 7 additions & 1 deletion src/start/RealmScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,13 @@ class RealmScreen extends PureComponent<Props, State> {
});
try {
const serverSettings: ApiResponseServerSettings = await api.getServerSettings(realm);
dispatch(realmAdd(realm, new ZulipVersion(serverSettings.zulip_version)));
dispatch(
realmAdd(
realm,
serverSettings.zulip_feature_level ?? 0,
new ZulipVersion(serverSettings.zulip_version),
),
);
dispatch(navigateToAuth(serverSettings));
Keyboard.dismiss();
} catch (err) {
Expand Down
20 changes: 20 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export type Account = {|
* Because a server deploy invalidates event queues, this means the value
* is always up to date for a server we have an active event queue on.
*
* This is `null` just when representing an account which was last used on
* a version of the app which didn't record this information.
*
* For use in:
* * how we make some API requests, in order to keep the logic isolated
* to the edge, where we communicate with the server, to keep with the
Expand All @@ -65,6 +68,23 @@ export type Account = {|
*/
zulipVersion: ZulipVersion | null,

/**
* An integer indicating what features are available on the server.
*
* The feature level increases monotonically; a value of N means the
* server supports all API features introduced before feature level N.
* This is designed to provide a simple way for mobile apps to decide
* whether the server supports a given feature or API change.
*
* This is `null` just when representing an account which was last used on
* a version of the app which didn't record this information.
*
* Like zulipVersion, we learn the feature level from /server_settings
* at the start of the login process, and again from /register when
* setting up an event queue.
*/
zulipFeatureLevel: number | null,

/**
* The last device token value the server has definitely heard from us.
*
Expand Down