mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-07 11:20:12 +00:00
* MM-70516 - Add ability to edit channel attribute values from Channel Info * Add API methods for patching property fields and setting channel attribute requirements * Update channel attribute clearing behavior for required and non-required fields * MM-70564 - use channel attributes on channel creation * Fix undefined propertyValues and ternary formatting in channel attributes * Fix spelling of "colour" to "color" in attribute chip tests and related comments * Add visibility checks for channel attribute labels in tests * Fix data-integrity, permission, and accessibility issues in channel attributes: scoped pruning, atomic writes, race serialization, required clear/stale-option guards * Fix handling of null values in apiGetChannelAttributeValue and update test assertions for channel attributes * Improve channel attribute editor sizing for long labels, keep Save visible, and prevent keyboard overlap on Android * Address CodeRabbit feedback for nullable values, bottom-sheet state, async validation, and reliable E2E cleanup * lock-safe field fetch, create-time gate recheck, scoped value pruning, and tappable header chips * Address CodeRabbit feedback: drop queued attribute saves on channel switch and use page object selectors in E2E * Respect the ChannelAttributesRequired flag on channel creation and enable it for channel attribute E2E * Skip required channel attribute E2E tests and stop enabling ChannelAttributesRequired on the shared server until they move to SITE_3 * Fix clearing channel attributes on iOS by sending an empty value instead of null, and stop E2E suites from turning off the ChannelAttributes flag mid-run * Refactor getEmptyPropertyValue to accept string type for fieldType parameter * E2E: give channel attribute test fields a suite-unique suffix Pool shards may only create optional property fields named <name>_<6 hex>; fixed names were blocked by the pool guard. * E2E: restore channel attribute cleanup checks; name network error codes Brings back the PR's afterEach/afterAll error handling that the main merge replaced, and makes a no-response error show its codes (an AggregateError has an empty message). * E2E: run channel attribute creation and editing specs on server B Their 11 required-attribute tests could not run on the shared servers. Both specs now run on the mutation server with ChannelAttributesRequired on for their run, and the tests are re-enabled. setChannelAttributesRequired moves to the shared helper. * Check header chips instead of their wrapper and use toExist for the create screen in channel attribute E2E --------- Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
132 lines
6.6 KiB
TypeScript
132 lines
6.6 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {getChannelAttributeEditorSnapPoints, SHEET_MAX_ROWS} from './utils';
|
|
|
|
// Mirrors the one mutation GenericBottomSheetRoute performs on whatever this
|
|
// helper returns, so ordering can be asserted the way it will actually be
|
|
// consumed rather than on the helper's raw output.
|
|
function applyRouteMargin(points: number[], margin: number): number[] {
|
|
const withMargin = [...points];
|
|
if (typeof withMargin[1] === 'number') {
|
|
withMargin[1] += margin;
|
|
}
|
|
return withMargin;
|
|
}
|
|
|
|
describe('getChannelAttributeEditorSnapPoints', () => {
|
|
const base = {
|
|
bottomInset: 0,
|
|
isTablet: false,
|
|
windowHeight: 800,
|
|
};
|
|
|
|
it('should return two ascending points ordered bottom to top', () => {
|
|
const [closed, open] = getChannelAttributeEditorSnapPoints({...base, fieldType: 'select', totalRows: 3});
|
|
|
|
expect(closed).toBe(1);
|
|
expect(open).toBeGreaterThan(closed);
|
|
});
|
|
|
|
it('should never resolve the open point above the window-height cap', () => {
|
|
const [, open] = getChannelAttributeEditorSnapPoints({...base, fieldType: 'select', totalRows: 3});
|
|
|
|
expect(open).toBeLessThanOrEqual(base.windowHeight * 0.8);
|
|
});
|
|
|
|
it('should never resolve the effective, post-route-margin open point above the window-height cap', () => {
|
|
const bottomInset = 34;
|
|
const points = getChannelAttributeEditorSnapPoints({...base, bottomInset, fieldType: 'select', totalRows: 3});
|
|
const [, effectiveOpen] = applyRouteMargin(points, bottomInset);
|
|
|
|
expect(effectiveOpen).toBeLessThanOrEqual(base.windowHeight * 0.8);
|
|
});
|
|
|
|
it('should cap the collapsed point on a short window instead of exceeding it', () => {
|
|
const [, open] = getChannelAttributeEditorSnapPoints({...base, windowHeight: 320, fieldType: 'select', totalRows: 3});
|
|
|
|
expect(open).toBeLessThanOrEqual(320 * 0.8);
|
|
});
|
|
|
|
it('should add a taller third point for an option field only when there are more rows than the collapsed estimate covers', () => {
|
|
const withinLimit = getChannelAttributeEditorSnapPoints({...base, fieldType: 'multiselect', totalRows: SHEET_MAX_ROWS});
|
|
const overLimit = getChannelAttributeEditorSnapPoints({...base, fieldType: 'multiselect', totalRows: SHEET_MAX_ROWS + 10});
|
|
|
|
expect(withinLimit).toHaveLength(2);
|
|
expect(overLimit).toHaveLength(3);
|
|
});
|
|
|
|
it('should keep the three points strictly ascending when a taller point is offered', () => {
|
|
const points = getChannelAttributeEditorSnapPoints({...base, fieldType: 'multiselect', totalRows: SHEET_MAX_ROWS + 10});
|
|
|
|
expect(points[0]).toBeLessThan(points[1]);
|
|
expect(points[1]).toBeLessThan(points[2]);
|
|
});
|
|
|
|
it('should never offer a taller point for a text field: it never has more than one row, and the caller opens it with keyboardBehavior="interactive" instead of relying on a taller snap point for the keyboard', () => {
|
|
const points = getChannelAttributeEditorSnapPoints({...base, fieldType: 'text', totalRows: 1});
|
|
|
|
expect(points).toHaveLength(2);
|
|
});
|
|
|
|
it('should stay ordered after GenericBottomSheetRoute applies its margin, for every window height that offers a third point', () => {
|
|
// Scans rather than hand-picking one windowHeight: what matters is that
|
|
// no window size in range ever produces an inversion once the route's
|
|
// margin lands on the second point, not that one particular value does.
|
|
let sawThreePoints = false;
|
|
for (let windowHeight = 300; windowHeight <= 1200; windowHeight += 10) {
|
|
const points = getChannelAttributeEditorSnapPoints({...base, windowHeight, bottomInset: 34, fieldType: 'multiselect', totalRows: SHEET_MAX_ROWS + 10});
|
|
if (points.length < 3) {
|
|
continue;
|
|
}
|
|
sawThreePoints = true;
|
|
|
|
const afterRoute = applyRouteMargin(points, 34);
|
|
expect(afterRoute[1]).toBeLessThan(afterRoute[2]);
|
|
}
|
|
|
|
expect(sawThreePoints).toBe(true);
|
|
});
|
|
|
|
it('should drop the taller point rather than risk an inversion when the collapsed estimate lands within the route margin of the cap', () => {
|
|
// Scans for a windowHeight where the collapsed point ends up close
|
|
// enough to the cap that adding the route's margin would invert it, and
|
|
// asserts the helper falls back to two points there instead.
|
|
let sawDroppedPoint = false;
|
|
for (let windowHeight = 300; windowHeight <= 1200; windowHeight += 10) {
|
|
const uncapped = getChannelAttributeEditorSnapPoints({...base, windowHeight, bottomInset: 0, fieldType: 'multiselect', totalRows: SHEET_MAX_ROWS + 10});
|
|
const points = getChannelAttributeEditorSnapPoints({...base, windowHeight, bottomInset: 34, fieldType: 'multiselect', totalRows: SHEET_MAX_ROWS + 10});
|
|
|
|
if (uncapped.length === 3 && points.length === 2) {
|
|
sawDroppedPoint = true;
|
|
}
|
|
}
|
|
|
|
expect(sawDroppedPoint).toBe(true);
|
|
});
|
|
|
|
it('should reserve extra height for the save button on text and multiselect fields but not select', () => {
|
|
const selectPoints = getChannelAttributeEditorSnapPoints({...base, fieldType: 'select', totalRows: 1});
|
|
const multiselectPoints = getChannelAttributeEditorSnapPoints({...base, fieldType: 'multiselect', totalRows: 1});
|
|
|
|
expect(multiselectPoints[1]).toBeGreaterThan(selectPoints[1]);
|
|
});
|
|
|
|
it('should account for the safe-area inset only when a save button is shown', () => {
|
|
const withoutInset = getChannelAttributeEditorSnapPoints({...base, fieldType: 'multiselect', totalRows: 1});
|
|
const withInset = getChannelAttributeEditorSnapPoints({...base, fieldType: 'multiselect', totalRows: 1, bottomInset: 34});
|
|
const selectWithoutInset = getChannelAttributeEditorSnapPoints({...base, fieldType: 'select', totalRows: 1});
|
|
const selectWithInset = getChannelAttributeEditorSnapPoints({...base, fieldType: 'select', totalRows: 1, bottomInset: 34});
|
|
|
|
expect(withInset[1]).toBe(withoutInset[1] + 34);
|
|
expect(selectWithInset[1]).toBe(selectWithoutInset[1]);
|
|
});
|
|
|
|
it('should reserve more separator margin on tablet than on phone', () => {
|
|
const phone = getChannelAttributeEditorSnapPoints({...base, fieldType: 'select', totalRows: 1, isTablet: false});
|
|
const tablet = getChannelAttributeEditorSnapPoints({...base, fieldType: 'select', totalRows: 1, isTablet: true});
|
|
|
|
expect(tablet[1]).toBeGreaterThan(phone[1]);
|
|
});
|
|
});
|