mirror of
https://github.com/apache/superset.git
synced 2026-08-04 04:52:32 +00:00
Adds an opt-in, consumption-only mobile experience (feature flag MOBILE_CONSUMPTION_MODE, default off, @lifecycle: development): - Dashboards: charts stacked full-width with real plugin dimensions (ChartHolder reports full column count on mobile; heights capped to the viewport minus chrome), sticky swipeable tab bars with gradient overflow affordances, filter bar in a drawer (FilterBar mobileMode), compact header (title scrolls away; edit/publish/fave/refresh controls hidden; dashboard info moved into the kebab menu) - Dashboard list: forced card view, full-width cards, search/filters and sort in a drawer (single FilterControls instance) - Home: dashboards-only Recents, compact empty states, desktop-only sections hidden - Navigation: hamburger drawer (dashboards, theme/language, user info/logout with row-tap navigation) - Route guarding: routes declare mobileSupported in routes.tsx; everything else renders a MobileUnsupported screen; viewport growth unblocks automatically (useIsMobile subscribes to matchMedia only when the flag is on, so flag-off deployments have zero render delta) - Serves a viewport meta tag (flag-gated) so mobile browsers lay out at device width instead of the ~980px legacy viewport; exposes is_feature_enabled to Jinja via the common context processor - User docs (using-superset/mobile-experience.mdx) with a Playwright screenshot generator following the docs:screenshots pattern - Docker dev config enables the flag; jest + Playwright coverage throughout Squashed from the iterative mobile-dashboard-support history (preserved at backup/mobile-pre-rebase-2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import { MemoryRouter } from 'react-router-dom';
|
|
import { render, screen } from 'spec/helpers/testing-library';
|
|
import { useIsMobile } from 'src/hooks/useIsMobile';
|
|
import MobileRouteGuard from '.';
|
|
|
|
jest.mock('src/hooks/useIsMobile', () => ({
|
|
useIsMobile: jest.fn(),
|
|
isMobileConsumptionEnabled: jest.fn().mockReturnValue(true),
|
|
}));
|
|
|
|
const mockedUseIsMobile = useIsMobile as jest.MockedFunction<
|
|
typeof useIsMobile
|
|
>;
|
|
|
|
const renderGuard = (mobileSupported?: boolean) =>
|
|
render(
|
|
<MemoryRouter initialEntries={['/some/route/']}>
|
|
<MobileRouteGuard mobileSupported={mobileSupported}>
|
|
<div data-test="guarded-content">Content</div>
|
|
</MobileRouteGuard>
|
|
</MemoryRouter>,
|
|
);
|
|
|
|
beforeEach(() => {
|
|
mockedUseIsMobile.mockReturnValue(false);
|
|
});
|
|
|
|
test('renders children on desktop regardless of mobileSupported', () => {
|
|
renderGuard(undefined);
|
|
expect(screen.getByTestId('guarded-content')).toBeInTheDocument();
|
|
});
|
|
|
|
test('renders children on mobile when the route is mobileSupported', () => {
|
|
mockedUseIsMobile.mockReturnValue(true);
|
|
renderGuard(true);
|
|
expect(screen.getByTestId('guarded-content')).toBeInTheDocument();
|
|
});
|
|
|
|
test('shows the unsupported screen on mobile for unsupported routes', () => {
|
|
mockedUseIsMobile.mockReturnValue(true);
|
|
renderGuard(undefined);
|
|
expect(screen.queryByTestId('guarded-content')).not.toBeInTheDocument();
|
|
expect(
|
|
screen.getByText("This view isn't available on mobile"),
|
|
).toBeInTheDocument();
|
|
});
|