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
64 changes: 48 additions & 16 deletions src/__tests__/lib/exampleData.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { randString, randInt } from '../../utils/misc';
import { makeUserId } from '../../api/idTypes';
import type { InitialData } from '../../api/apiTypes';
import { EventTypes, type UpdateMessageEvent } from '../../api/eventTypes';
import { CreateWebPublicStreamPolicy } from '../../api/permissionsTypes';
import type {
AccountSwitchAction,
LoginSuccessAction,
Expand Down Expand Up @@ -703,7 +704,7 @@ export const action = Object.freeze({
}): LoginSuccessAction),

/**
* A minimal well-typed REGISTER_COMPLETE action.
* A minimal well-typed REGISTER_COMPLETE action from a recent server.
*
* Beware! The data here may not be representative. Generally each test
* should specify the particular properties that it cares about.
Expand All @@ -714,28 +715,44 @@ export const action = Object.freeze({
register_complete: (deepFreeze({
type: REGISTER_COMPLETE,
data: {
// InitialDataBase
last_event_id: 34,
msg: '',
queue_id: '1',
zulip_feature_level: 1,
zulip_version: zulipVersion.raw(),

// InitialDataAlertWords
alert_words: [],

// InitialDataMessage
max_message_id: 100,

// InitialDataMutedTopics
muted_topics: [],

// InitialDataMutedUsers
muted_users: [],

// InitialDataPresence
presences: {},

// InitialDataRealm
development_environment: false,
event_queue_longpoll_timeout_seconds: 600,
// jitsi_server_url omitted
max_avatar_file_size_mib: 3,
max_file_upload_size_mib: 3,
max_icon_file_size: 3,
max_icon_file_size_mib: 3,
max_logo_file_size_mib: 3,
max_message_length: 10000,
max_stream_description_length: 500,
max_stream_name_length: 100,
max_topic_length: 50,
password_min_length: 8,
password_min_guesses: 3,
realm_add_custom_emoji_policy: 3,
realm_add_emoji_by_admins_only: true,
realm_allow_community_topic_editing: true,
realm_allow_edit_history: true,
realm_allow_message_deleting: true,
realm_allow_message_editing: true,
realm_authentication_methods: { GitHub: true, Email: true, Google: true },
realm_available_video_chat_providers: {},
Expand All @@ -745,11 +762,10 @@ export const action = Object.freeze({
realm_community_topic_editing_limit_seconds: 600,
realm_create_private_stream_policy: 3,
realm_create_public_stream_policy: 3,
realm_create_web_public_stream_policy: 3,
realm_create_web_public_stream_policy: CreateWebPublicStreamPolicy.ModeratorOrAbove,
realm_default_code_block_language: 'python',
realm_default_external_accounts: {},
realm_default_language: 'en',
realm_default_twenty_four_hour_time: true,
realm_delete_own_message_policy: 3,
realm_description: 'description',
realm_digest_emails_enabled: true,
Expand All @@ -760,12 +776,12 @@ export const action = Object.freeze({
realm_email_auth_enabled: true,
realm_email_changes_disabled: true,
realm_emails_restricted_to_domains: false,
realm_enable_spectator_access: true,
realm_giphy_rating: 3,
realm_icon_source: 'U',
realm_icon_url: 'example.com/some/path',
realm_inline_image_preview: true,
realm_inline_url_embed_preview: true,
realm_invite_by_admins_only: true,
realm_invite_required: true,
realm_invite_to_realm_policy: 3,
realm_invite_to_stream_policy: 3,
Expand Down Expand Up @@ -797,26 +813,29 @@ export const action = Object.freeze({
realm_waiting_period_threshold: 3,
realm_wildcard_mention_policy: 3,
server_avatar_changes_disabled: false,
server_generation: 3,
server_inline_image_preview: true,
server_inline_url_embed_preview: true,
server_name_changes_disabled: false,
server_needs_upgrade: false,
server_inline_url_embed_preview: true,
server_inline_image_preview: true,
server_generation: 3,
server_web_public_streams_enabled: true,
settings_send_digest_emails: true,
upgrade_text_for_wide_organization_logo: '',
zulip_plan_is_not_limited: false,
zulip_feature_level: 1,
zulip_version: zulipVersion.raw(),

// InitialDataRealmEmoji
realm_emoji: {},

// InitialDataRealmFilters
realm_filters: [],

// RawInitialDataRealmUser
// InitialDataRealmUser
realm_users: [],
realm_non_active_users: [],
avatar_source: 'G',
avatar_url_medium: 'url',
avatar_url: null, // ideally would agree with selfUser.avatar_url
can_create_streams: false,
can_create_streams: false, // new servers don't send, but we fill in
can_create_public_streams: false,
can_create_private_streams: false,
can_create_web_public_streams: false,
Expand All @@ -833,19 +852,30 @@ export const action = Object.freeze({
full_name: selfUser.full_name,
cross_realm_bots: [],

// InitialDataRealmUserGroups
realm_user_groups: [],

// InitialDataRecentPmConversations
recent_private_conversations: [],

// InitialDataStream
streams: [],

// InitialDataSubscription
never_subscribed: [],
subscriptions: [],
unsubscribed: [],

// InitialDataUpdateMessageFlags
unread_msgs: {
streams: [],
huddles: [],
count: 0,
pms: [],
mentions: [],
count: 0,
},

// InitialDataUserSettings
user_settings: {
twenty_four_hour_time: true,
dense_mode: true,
Expand Down Expand Up @@ -890,6 +920,8 @@ export const action = Object.freeze({
send_stream_typing_notifications: true,
send_read_receipts: true,
},

// InitialDataUserStatus
user_status: {},
},
}): RegisterCompleteAction),
Expand Down
130 changes: 130 additions & 0 deletions src/__tests__/permissionSelectors-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/* @flow strict-local */
import invariant from 'invariant';

import { tryGetActiveAccountState } from '../selectors';
import {
Role,
type RoleT,
CreateWebPublicStreamPolicy,
type CreateWebPublicStreamPolicyT,
} from '../api/permissionsTypes';
import { getCanCreateWebPublicStreams } from '../permissionSelectors';
import rootReducer from '../boot/reducers';
import { EVENT_USER_UPDATE } from '../actionConstants';
import * as eg from './lib/exampleData';

describe('getCanCreateWebPublicStreams', () => {
const { AdminOrAbove, ModeratorOrAbove, Nobody, OwnerOnly } = CreateWebPublicStreamPolicy;
const { Owner, Admin, Moderator, Member, Guest } = Role;

test('returns false when webPublicStreamsEnabled is false', () => {
const globalState = [
{
type: EVENT_USER_UPDATE,
id: 0,
userId: eg.selfUser.user_id,
person: { user_id: eg.selfUser.user_id, role: Owner },
},
].reduce(
rootReducer,
eg.reduxStatePlus({
realm: {
...eg.plusReduxState.realm,
webPublicStreamsEnabled: false,
enableSpectatorAccess: true,
createWebPublicStreamPolicy: ModeratorOrAbove,
},
}),
Comment on lines +30 to +37
Copy link
Copy Markdown
Collaborator Author

@chrisbobbe chrisbobbe May 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be nice to use a completely empty initial state here, then apply changes like createWebPublicStreamPolicy in a minimally modified eg.action.register_complete. The problem is that eg.reduxStatePlus gives us boring things that we need, like putting selfUser in state.users, that we don't get from eg.action.register_complete.

What do we think about a TODO to change eg.action.register_complete's interface such that it would make a blank state look more like eg.plusReduxState? We could even construct eg.plusReduxState using this modified eg.action.register_complete.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to change eg.action.register_complete's interface such that it would make a blank state look more like eg.plusReduxState? We could even construct eg.plusReduxState using this modified eg.action.register_complete.

Yeah, that sounds like a good direction.

I'm not sure if we have a use case that needs a "minimal" version of the action, which is what that's currently documented as. Would have to look at the use sites of it to see. If we do, we could potentially have both -- first the minimal, then also one that's defined by starting from that and adding things we need to add.

);

const state = tryGetActiveAccountState(globalState);
invariant(state, 'expected per-account state');

expect(getCanCreateWebPublicStreams(state)).toBeFalse();
});

test('returns false when enableSpectatorAccess is false', () => {
const globalState = [
{
type: EVENT_USER_UPDATE,
id: 0,
userId: eg.selfUser.user_id,
person: { user_id: eg.selfUser.user_id, role: Owner },
},
].reduce(
rootReducer,
eg.reduxStatePlus({
realm: {
...eg.plusReduxState.realm,
webPublicStreamsEnabled: true,
enableSpectatorAccess: false,
createWebPublicStreamPolicy: ModeratorOrAbove,
},
}),
);

const state = tryGetActiveAccountState(globalState);
invariant(state, 'expected per-account state');

expect(getCanCreateWebPublicStreams(state)).toBeFalse();
});

test.each`
policy | role | expected
${AdminOrAbove} | ${Owner} | ${true}
${AdminOrAbove} | ${Admin} | ${true}
${AdminOrAbove} | ${Moderator} | ${false}
${AdminOrAbove} | ${Member} | ${false}
${AdminOrAbove} | ${Guest} | ${false}
${ModeratorOrAbove} | ${Owner} | ${true}
${ModeratorOrAbove} | ${Admin} | ${true}
Comment on lines +72 to +80
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat!

${ModeratorOrAbove} | ${Moderator} | ${true}
${ModeratorOrAbove} | ${Member} | ${false}
${ModeratorOrAbove} | ${Guest} | ${false}
${Nobody} | ${Owner} | ${false}
${Nobody} | ${Admin} | ${false}
${Nobody} | ${Moderator} | ${false}
${Nobody} | ${Member} | ${false}
${Nobody} | ${Guest} | ${false}
${OwnerOnly} | ${Owner} | ${true}
${OwnerOnly} | ${Admin} | ${false}
${OwnerOnly} | ${Moderator} | ${false}
${OwnerOnly} | ${Member} | ${false}
${OwnerOnly} | ${Guest} | ${false}
`(
'returns $expected when policy is $policy and role is $role',
async ({
policy,
role,
expected,
}: {
policy: CreateWebPublicStreamPolicyT,
role: RoleT,
expected: boolean,
}) => {
const globalState = [
{
type: EVENT_USER_UPDATE,
id: 0,
userId: eg.selfUser.user_id,
person: { user_id: eg.selfUser.user_id, role },
},
].reduce(
rootReducer,
eg.reduxStatePlus({
realm: {
...eg.plusReduxState.realm,
webPublicStreamsEnabled: true,
enableSpectatorAccess: true,
createWebPublicStreamPolicy: policy,
},
}),
);

const state = tryGetActiveAccountState(globalState);
invariant(state, 'expected per-account state');

expect(getCanCreateWebPublicStreams(state)).toBe(expected);
},
);
});
1 change: 1 addition & 0 deletions src/api/apiTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export type * from './transportTypes';
export type * from './modelTypes';
export type * from './eventTypes';
export type * from './initialDataTypes';
export type * from './permissionsTypes';
6 changes: 4 additions & 2 deletions src/api/initialDataTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
UserGroup,
UserId,
} from './apiTypes';
import type { CreateWebPublicStreamPolicyT } from './permissionsTypes';

/*
The types in this file are organized by which `fetch_event_types` values
Expand Down Expand Up @@ -163,8 +164,9 @@ export type InitialDataRealm = $ReadOnly<{|
// realm_create_private_stream_policy and realm_create_public_stream_policy
realm_create_stream_policy?: number,

// TODO(server-5.0): Added in feat. 103; when absent, treat as 6 (nobody).
realm_create_web_public_stream_policy?: number,
// TODO(server-5.0): Added in feat. 103; when absent, treat as
// CreateWebPublicStreamPolicy.Nobody.
realm_create_web_public_stream_policy?: CreateWebPublicStreamPolicyT,

realm_default_code_block_language: string | null,
realm_default_external_accounts: {|
Expand Down
5 changes: 3 additions & 2 deletions src/api/modelTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import type { AvatarURL } from '../utils/avatar';
import type { UserId } from './idTypes';
import type { RoleT } from './permissionsTypes';

export type * from './idTypes';

Expand Down Expand Up @@ -125,7 +126,7 @@ export type User = {|
+bot_owner?: string,

// TODO(server-4.0): New in FL 59
+role?: number,
+role?: RoleT,

// The ? is for future-proofing. Greg explains in 2020-02, at
// https://github.com/zulip/zulip-mobile/pull/3789#discussion_r378554698 ,
Expand Down Expand Up @@ -212,7 +213,7 @@ export type CrossRealmBot = {|
// +bot_owner?: string,

// TODO(server-4.0): New in FL 59
+role?: number,
+role?: RoleT,

// The ? is for future-proofing. For bots it's always '':
// https://github.com/zulip/zulip-mobile/pull/3789#issuecomment-581218576
Expand Down
Loading