mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-25 03:49:11 +00:00
feat(common): port utils, tests currently broken
This commit is contained in:
parent
c7afd84e1e
commit
5b440ffa8d
10 changed files with 235 additions and 0 deletions
|
@ -11,6 +11,7 @@ def _render_deps(deps = []):
|
||||||
if has_added_grpc_deps == False:
|
if has_added_grpc_deps == False:
|
||||||
output_deps.extend([
|
output_deps.extend([
|
||||||
"@npm//google-protobuf",
|
"@npm//google-protobuf",
|
||||||
|
"@npm//@types/google-protobuf",
|
||||||
"@npm//@improbable-eng/grpc-web",
|
"@npm//@improbable-eng/grpc-web",
|
||||||
])
|
])
|
||||||
has_added_grpc_deps = True
|
has_added_grpc_deps = True
|
||||||
|
|
23
src/common/utils/BUILD.bazel
Normal file
23
src/common/utils/BUILD.bazel
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
load("//:hack/react.bzl", "react_library")
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
react_library(
|
||||||
|
name = "utils",
|
||||||
|
deps = [
|
||||||
|
"chroma-js",
|
||||||
|
"react",
|
||||||
|
"styled-components",
|
||||||
|
"//src/rpc/shared",
|
||||||
|
"@types/chroma-js",
|
||||||
|
"@types/react",
|
||||||
|
"@types/styled-components",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# jest_test(
|
||||||
|
# src = ":utils",
|
||||||
|
# deps = [
|
||||||
|
# "//hack/fixtures",
|
||||||
|
# ],
|
||||||
|
# )
|
9
src/common/utils/ReactifyNewlines.spec.tsx
Normal file
9
src/common/utils/ReactifyNewlines.spec.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ReactifyNewlines } from './ReactifyNewlines';
|
||||||
|
|
||||||
|
it('renders a correct number of divs per newlines', () => {
|
||||||
|
const view = shallow(<ReactifyNewlines>{`1\n2\n3`}</ReactifyNewlines>);
|
||||||
|
|
||||||
|
expect(view.find('div').length).toBe(3);
|
||||||
|
});
|
12
src/common/utils/ReactifyNewlines.tsx
Normal file
12
src/common/utils/ReactifyNewlines.tsx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export const ReactifyNewlines = (props: { children: string }) => {
|
||||||
|
const textArray = props.children.split('\n');
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{textArray.map((part, idx) => (
|
||||||
|
<div key={`rifynl${idx}`}>{part || <> </>}</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
37
src/common/utils/hasPermission.spec.ts
Normal file
37
src/common/utils/hasPermission.spec.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { hasPermission, permissions, hasPermissionOrAdmin } from './hasPermission';
|
||||||
|
import { Role } from 'roleypoly/src/rpc/shared';
|
||||||
|
import { guildRoles } from 'roleypoly/hack/fixtures/storyData';
|
||||||
|
|
||||||
|
const roles: Role.AsObject[] = [
|
||||||
|
{
|
||||||
|
...guildRoles.rolesList[0],
|
||||||
|
permissions: permissions.ADMINISTRATOR,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...guildRoles.rolesList[0],
|
||||||
|
permissions:
|
||||||
|
permissions.SPEAK | permissions.BAN_MEMBERS | permissions.CHANGE_NICKNAME,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...guildRoles.rolesList[0],
|
||||||
|
permissions: permissions.BAN_MEMBERS,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it('finds a permission within a list of roles', () => {
|
||||||
|
const result = hasPermission(roles, permissions.CHANGE_NICKNAME);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('finds admin within a list of roles', () => {
|
||||||
|
const result = hasPermissionOrAdmin(roles, permissions.BAN_MEMBERS);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not find a permission within a list of roles without one', () => {
|
||||||
|
const result = hasPermission(roles, permissions.KICK_MEMBERS);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
45
src/common/utils/hasPermission.ts
Normal file
45
src/common/utils/hasPermission.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { Role } from 'roleypoly/src/rpc/shared';
|
||||||
|
|
||||||
|
export const hasPermission = (roles: Role.AsObject[], permission: number): boolean => {
|
||||||
|
const aggregateRoles = roles.reduce((acc, role) => acc | role.permissions, 0);
|
||||||
|
return (aggregateRoles & permission) === permission;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hasPermissionOrAdmin = (
|
||||||
|
roles: Role.AsObject[],
|
||||||
|
permission: number
|
||||||
|
): boolean => hasPermission(roles, permission | permissions.ADMINISTRATOR);
|
||||||
|
|
||||||
|
export const permissions = {
|
||||||
|
CREATE_INSTANT_INVITE: 0x1,
|
||||||
|
KICK_MEMBERS: 0x2,
|
||||||
|
BAN_MEMBERS: 0x4,
|
||||||
|
ADMINISTRATOR: 0x8,
|
||||||
|
MANAGE_CHANNELS: 0x10,
|
||||||
|
MANAGE_GUILD: 0x20,
|
||||||
|
ADD_REACTIONS: 0x40,
|
||||||
|
VIEW_AUDIT_LOG: 0x80,
|
||||||
|
VIEW_CHANNEL: 0x400,
|
||||||
|
SEND_MESSAGES: 0x800,
|
||||||
|
SEND_TTS_MESSAGES: 0x1000,
|
||||||
|
MANAGE_MESSAGES: 0x2000,
|
||||||
|
EMBED_LINKS: 0x4000,
|
||||||
|
ATTACH_FILES: 0x8000,
|
||||||
|
READ_MESSAGE_HISTORY: 0x10000,
|
||||||
|
MENTION_EVERYONE: 0x20000,
|
||||||
|
USE_EXTERNAL_EMOJIS: 0x40000,
|
||||||
|
VIEW_GUILD_INSIGHTS: 0x80000,
|
||||||
|
CONNECT: 0x100000,
|
||||||
|
SPEAK: 0x200000,
|
||||||
|
MUTE_MEMBERS: 0x400000,
|
||||||
|
DEAFEN_MEMBERS: 0x800000,
|
||||||
|
MOVE_MEMBERS: 0x1000000,
|
||||||
|
USE_VAD: 0x2000000,
|
||||||
|
PRIORITY_SPEAKER: 0x100,
|
||||||
|
STREAM: 0x200,
|
||||||
|
CHANGE_NICKNAME: 0x4000000,
|
||||||
|
MANAGE_NICKNAMES: 0x8000000,
|
||||||
|
MANAGE_ROLES: 0x10000000,
|
||||||
|
MANAGE_WEBHOOKS: 0x20000000,
|
||||||
|
MANAGE_EMOJIS: 0x40000000,
|
||||||
|
};
|
9
src/common/utils/protoReflection.spec.ts
Normal file
9
src/common/utils/protoReflection.spec.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { DiscordUser } from 'roleypoly/src/rpc/shared';
|
||||||
|
import { user } from 'roleypoly/hack/fixtures/storyData';
|
||||||
|
import { AsObjectToProto } from './protoReflection';
|
||||||
|
|
||||||
|
it('converts a RoleypolyUser.AsObject back to protobuf', () => {
|
||||||
|
const proto = AsObjectToProto(DiscordUser, user);
|
||||||
|
|
||||||
|
expect(proto.toObject()).toMatchObject(user);
|
||||||
|
});
|
30
src/common/utils/protoReflection.ts
Normal file
30
src/common/utils/protoReflection.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import * as pbjs from 'google-protobuf';
|
||||||
|
|
||||||
|
type GenericObject<T extends pbjs.Message> = T;
|
||||||
|
type ProtoFunction<T extends pbjs.Message, U extends ReturnType<T['toObject']>> = (
|
||||||
|
value: U[keyof U]
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export const AsObjectToProto = <T extends pbjs.Message>(
|
||||||
|
protoClass: { new (): T },
|
||||||
|
input: ReturnType<T['toObject']>
|
||||||
|
): GenericObject<T> => {
|
||||||
|
const proto = new protoClass();
|
||||||
|
const protoKeys = Object.getOwnPropertyNames((proto as any).__proto__);
|
||||||
|
|
||||||
|
for (let inputKey in input) {
|
||||||
|
const setCallName = protoKeys.find(
|
||||||
|
(key) => `set${inputKey.toLowerCase()}` === key.toLowerCase()
|
||||||
|
) as keyof typeof proto;
|
||||||
|
|
||||||
|
if (!setCallName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
((proto[setCallName] as unknown) as ProtoFunction<T, typeof input>)(
|
||||||
|
input[inputKey]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return proto;
|
||||||
|
};
|
48
src/common/utils/sortBy.spec.ts
Normal file
48
src/common/utils/sortBy.spec.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { sortBy } from './sortBy';
|
||||||
|
|
||||||
|
it('sorts an array of objects by its key', () => {
|
||||||
|
const output = sortBy(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'bbb',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'aaa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ddd',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ccc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'name'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(output.map((v) => v.name)).toEqual(['aaa', 'bbb', 'ccc', 'ddd']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sorts an array of objects by its key with a predicate', () => {
|
||||||
|
const output = sortBy(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'cc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bbb',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'aaaa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'd',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'name',
|
||||||
|
(a, b) => {
|
||||||
|
return a.length > b.length ? 1 : -1;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(output.map((v) => v.name)).toEqual(['d', 'cc', 'bbb', 'aaaa']);
|
||||||
|
});
|
21
src/common/utils/sortBy.ts
Normal file
21
src/common/utils/sortBy.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
export const sortBy = <T>(
|
||||||
|
array: T[],
|
||||||
|
key: keyof T,
|
||||||
|
predicate?: (a: T[keyof T], b: T[keyof T]) => number
|
||||||
|
) => {
|
||||||
|
return array.sort((a, b) => {
|
||||||
|
if (predicate) {
|
||||||
|
return predicate(a[key], b[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a[key] === b[key]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a[key] > b[key]) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue