From 80c0c706406fc21c886158c90ce21de6f642d104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Mon, 16 Sep 2024 23:18:06 +0200 Subject: [PATCH] Minimal integration test for dartdoc search bar. (#8056) --- pkg/pub_integration/lib/src/test_browser.dart | 44 +++++++++---- .../test/dartdoc_search_test.dart | 65 +++++++++++++++++++ .../test/search_completition_test.dart | 11 +--- 3 files changed, 97 insertions(+), 23 deletions(-) create mode 100644 pkg/pub_integration/test/dartdoc_search_test.dart diff --git a/pkg/pub_integration/lib/src/test_browser.dart b/pkg/pub_integration/lib/src/test_browser.dart index 322e1840f..04a59e8ec 100644 --- a/pkg/pub_integration/lib/src/test_browser.dart +++ b/pkg/pub_integration/lib/src/test_browser.dart @@ -182,13 +182,23 @@ class TestBrowserSession { await rq.continueRequest(); return; } + // TODO: this file is missing, we may need to fix it in the dartdoc stylesheet + if (rq.url.endsWith('/css/search.svg')) { + await rq.respond( + status: 200, + body: '', + contentType: 'image/svg+xml', + headers: {'Cache-Control': 'public, max-age=604800'}, + ); + return; + } final uri = Uri.parse(rq.url); if (uri.path.contains('//')) { serverErrors.add('Double-slash URL detected: "${rq.url}".'); } - await rq.continueRequest(headers: rq.headers); + await rq.continueRequest(); }); page.onResponse.listen((rs) async { @@ -209,21 +219,29 @@ class TestBrowserSession { try { parseAndValidateHtml(await rs.text); } catch (e) { - serverErrors.add('${rs.request.url} returned bad HTML: $e'); + final url = rs.request.url; + if (url.contains('/documentation/') && + url.endsWith('-sidebar.html')) { + // ignore dartdoc sidebars + } else { + serverErrors.add('$url returned bad HTML: $e'); + } } } - final uri = Uri.parse(rs.url); - if (uri.pathSegments.length > 1 && uri.pathSegments.first == 'static') { - if (!uri.pathSegments[1].startsWith('hash-')) { - serverErrors.add('Static ${rs.url} is without hash URL.'); - } - - final cacheHeader = rs.headers[HttpHeaders.cacheControlHeader]; - if (cacheHeader == null || - !cacheHeader.contains('public') || - !cacheHeader.contains('max-age')) { - serverErrors.add('Static ${rs.url} is without public caching.'); + if (!rs.url.startsWith('data:')) { + final uri = Uri.parse(rs.url); + if (uri.pathSegments.length > 1 && uri.pathSegments.first == 'static') { + if (!uri.pathSegments[1].startsWith('hash-')) { + serverErrors.add('Static ${rs.url} is without hash URL.'); + } + + final cacheHeader = rs.headers[HttpHeaders.cacheControlHeader]; + if (cacheHeader == null || + !cacheHeader.contains('public') || + !cacheHeader.contains('max-age')) { + serverErrors.add('Static ${rs.url} is without public caching.'); + } } } }); diff --git a/pkg/pub_integration/test/dartdoc_search_test.dart b/pkg/pub_integration/test/dartdoc_search_test.dart new file mode 100644 index 000000000..17e18f598 --- /dev/null +++ b/pkg/pub_integration/test/dartdoc_search_test.dart @@ -0,0 +1,65 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:pub_integration/src/fake_test_context_provider.dart'; +import 'package:pub_integration/src/test_browser.dart'; +import 'package:puppeteer/puppeteer.dart'; +import 'package:test/test.dart'; + +void main() { + group('dartdoc search', () { + late final TestContextProvider fakeTestScenario; + final httpClient = http.Client(); + + setUpAll(() async { + fakeTestScenario = await TestContextProvider.start(); + }); + + tearDownAll(() async { + await fakeTestScenario.close(); + httpClient.close(); + }); + + test('bulk tests', () async { + final origin = fakeTestScenario.pubHostedUrl; + // Importing one package + local analysis + await httpClient.post(Uri.parse('$origin/fake-test-profile'), + body: json.encode({ + 'testProfile': { + 'defaultUser': 'admin@pub.dev', + 'packages': [ + {'name': 'oxygen'}, + ], + }, + 'analysis': 'local', + })); + + final user = await fakeTestScenario.createAnonymousTestUser(); + + // test keyboard navigation + await user.withBrowserPage((page) async { + await page.gotoOrigin('/documentation/oxygen/latest/'); + + await page.keyboard.press(Key.slash); + await Future.delayed(Duration(milliseconds: 200)); + await page.keyboard.type('enum'); + await Future.delayed(Duration(milliseconds: 200)); + await page.keyboard.press(Key.arrowDown); + await Future.delayed(Duration(milliseconds: 200)); + await page.keyboard.press(Key.enter); + + await page.waitForNavigation(); + + // It is likely that we end up on the `TypeEnum.html` page, but we don't + // need to hardcode it here, in case dartdoc changes the order of the options. + expect(page.url, + startsWith('$origin/documentation/oxygen/latest/oxygen/')); + expect(page.url, endsWith('.html')); + }); + }); + }, timeout: Timeout.factor(testTimeoutFactor)); +} diff --git a/pkg/pub_integration/test/search_completition_test.dart b/pkg/pub_integration/test/search_completition_test.dart index 645c2682b..12d8ffd71 100644 --- a/pkg/pub_integration/test/search_completition_test.dart +++ b/pkg/pub_integration/test/search_completition_test.dart @@ -26,16 +26,7 @@ void main() { test('bulk tests', () async { final origin = fakeTestScenario.pubHostedUrl; - // init server data - // - // The test profile import uses a fake analysis by default, which - // assigns tags with a pseudorandom process (based on the hash of the - // package's name and sometimes the version), with a few hardcoded - // patterns, e.g. `flutter_*` packages will get `sdk:flutter` tag assigned. - // - // This imports 100 packages with these semi-random tags, and adding and - // removing filters works because of the number of packages and their - // tags are kind of random. + // Importing one package + local analysis await httpClient.post(Uri.parse('$origin/fake-test-profile'), body: json.encode({ 'testProfile': {