diff --git a/content/copilot/concepts/about-copilot-coding-agent.md b/content/copilot/concepts/about-copilot-coding-agent.md index 63168558c0a7..fd622cc1c55e 100644 --- a/content/copilot/concepts/about-copilot-coding-agent.md +++ b/content/copilot/concepts/about-copilot-coding-agent.md @@ -111,8 +111,11 @@ Users can include hidden messages in issues assigned to {% data variables.produc * **{% data variables.product.prodname_copilot_short %} doesn't account for content exclusions**. Content exclusions allow administrators to configure {% data variables.product.prodname_copilot_short %} to ignore certain files. When using {% data variables.copilot.copilot_coding_agent %}, {% data variables.product.prodname_copilot_short %} will not ignore these files, and will be able to see and update them. See [AUTOTITLE](/copilot/managing-copilot/configuring-and-auditing-content-exclusion/excluding-content-from-github-copilot). * **{% data variables.copilot.copilot_coding_agent %} is not available in {% data variables.enterprise.data_residency %}**. The agent is only available in {% data variables.product.prodname_dotcom_the_website %}. +## Hands-on practice + +Try the [Expand your team with {% data variables.copilot.copilot_coding_agent %}](https://github.com/skills/expand-your-team-with-copilot/) Skills exercise for practical experience with {% data variables.copilot.copilot_coding_agent %}. + ## Further reading -* **Hands-on practice**: Try the [Expand your team with {% data variables.copilot.copilot_coding_agent %}](https://github.com/skills/expand-your-team-with-copilot/) Skills course for practical experience with {% data variables.copilot.copilot_coding_agent %}. * [AUTOTITLE](/copilot/using-github-copilot/coding-agent) how-to articles * [AUTOTITLE](/copilot/responsible-use-of-github-copilot-features/responsible-use-of-copilot-coding-agent-on-githubcom) diff --git a/content/copilot/tutorials/modernizing-legacy-code-with-github-copilot.md b/content/copilot/tutorials/modernizing-legacy-code-with-github-copilot.md index fb3c6a6f2d0c..8233b86c10d9 100644 --- a/content/copilot/tutorials/modernizing-legacy-code-with-github-copilot.md +++ b/content/copilot/tutorials/modernizing-legacy-code-with-github-copilot.md @@ -390,6 +390,10 @@ In this example, we looked at an account management system written in COBOL and * **Review the code before using it**: Make sure you understand the code that {% data variables.product.prodname_copilot_short %} provides before using it in your application. This will help you catch any potential issues and ensure that the code meets your requirements. * **Validate your changes**: After making changes to the code, it's important to validate that the application still works as expected. You can use the test plan generated by {% data variables.product.prodname_copilot_short %} to create unit and integration tests for the application. +## Hands-on practice + +Try the [Modernizing your legacy code with {% data variables.product.prodname_copilot %}](https://github.com/skills/modernize-your-legacy-code-with-github-copilot) Skills exercise for practical experience updating a legacy codebase with {% data variables.product.prodname_copilot %}. + ## Further reading * [AUTOTITLE](/copilot/copilot-chat-cookbook/documenting-code/documenting-legacy-code) diff --git a/src/archives/tests/deprecated-enterprise-versions.ts b/src/archives/tests/deprecated-enterprise-versions.ts index 541e6cf6b5c0..3515fb7bb587 100644 --- a/src/archives/tests/deprecated-enterprise-versions.ts +++ b/src/archives/tests/deprecated-enterprise-versions.ts @@ -1,7 +1,7 @@ import { describe, expect, test, vi } from 'vitest' import enterpriseServerReleases from '@/versions/lib/enterprise-server-releases' -import { get, getDOM } from '@/tests/helpers/e2etest-ts' +import { get, getDOM } from '@/tests/helpers/e2etest' import { SURROGATE_ENUMS } from '@/frame/middleware/set-fastly-surrogate-key' describe('enterprise deprecation', () => { diff --git a/src/fixtures/tests/images.ts b/src/fixtures/tests/images.ts index dda711defbec..7db30efc83ee 100644 --- a/src/fixtures/tests/images.ts +++ b/src/fixtures/tests/images.ts @@ -35,7 +35,7 @@ describe('render Markdown image tags', () => { // When transformed as a source in a `` tag, it's automatically // injected with the `mw-XXXXX` virtual indicator in the URL that // resizes it on-the-fly. - const image = sharp(res.body as Buffer) + const image = sharp(Buffer.from(res.body as ArrayBuffer)) const { width, height } = await image.metadata() expect(width).toBe(MAX_WIDTH) // The `_fixtures/screenshot.png` is 2000x1494. diff --git a/src/search/tests/api-ai-search-autocomplete.ts b/src/search/tests/api-ai-search-autocomplete.ts index 5daea89a0fa8..d4f8c6769484 100644 --- a/src/search/tests/api-ai-search-autocomplete.ts +++ b/src/search/tests/api-ai-search-autocomplete.ts @@ -14,7 +14,7 @@ import { expect, test, vi } from 'vitest' import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs' -import { get } from '@/tests/helpers/e2etest-ts' +import { get } from '@/tests/helpers/e2etest' import type { AutocompleteSearchResponse } from '@/search/types' diff --git a/src/search/tests/api-combined-search.ts b/src/search/tests/api-combined-search.ts index f0e91dd8d2b7..8f01358ff4e4 100644 --- a/src/search/tests/api-combined-search.ts +++ b/src/search/tests/api-combined-search.ts @@ -14,7 +14,7 @@ import { expect, test, vi } from 'vitest' import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs' -import { get } from '@/tests/helpers/e2etest-ts' +import { get } from '@/tests/helpers/e2etest' import type { CombinedSearchResponse } from '@/search/types' diff --git a/src/search/tests/api-search.ts b/src/search/tests/api-search.ts index aaee24a2fe45..a77abcdb9144 100644 --- a/src/search/tests/api-search.ts +++ b/src/search/tests/api-search.ts @@ -13,7 +13,7 @@ import { expect, test, vi } from 'vitest' import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs' -import { get } from '@/tests/helpers/e2etest-ts' +import { get } from '@/tests/helpers/e2etest' import { GeneralSearchResponse, SearchResultAggregations, GeneralSearchHit } from '@/search/types' if (!process.env.ELASTICSEARCH_URL) { diff --git a/src/search/tests/rendering.ts b/src/search/tests/rendering.ts index 711301a47dfc..61d95c7641cb 100644 --- a/src/search/tests/rendering.ts +++ b/src/search/tests/rendering.ts @@ -14,7 +14,7 @@ import { expect, test, vi } from 'vitest' import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs' -import { get, getDOM } from '@/tests/helpers/e2etest-ts' +import { get, getDOM } from '@/tests/helpers/e2etest' import { SURROGATE_ENUMS } from '@/frame/middleware/set-fastly-surrogate-key' if (!process.env.ELASTICSEARCH_URL) { diff --git a/src/search/tests/search.ts b/src/search/tests/search.ts index 6ee506ad73e9..17529ed272e6 100644 --- a/src/search/tests/search.ts +++ b/src/search/tests/search.ts @@ -1,5 +1,5 @@ import { describe, expect, test, vi } from 'vitest' -import { get, getDOM } from '@/tests/helpers/e2etest-ts' +import { get, getDOM } from '@/tests/helpers/e2etest' describe('search results page', () => { vi.setConfig({ testTimeout: 60 * 1000 }) diff --git a/src/tests/helpers/e2etest.js b/src/tests/helpers/e2etest.js deleted file mode 100644 index 7aede0687042..000000000000 --- a/src/tests/helpers/e2etest.js +++ /dev/null @@ -1,81 +0,0 @@ -import cheerio from 'cheerio' -import got from 'got' -import { omitBy, isUndefined } from 'lodash-es' - -export async function get( - route, - { - method = 'get', - body = undefined, - followRedirects = false, - followAllRedirects = false, - headers = {}, - responseType = '', - retries = 0, - } = {}, -) { - const fn = got[method] - if (!fn || typeof fn !== 'function') throw new Error(`No method function for '${method}'`) - const xopts = omitBy( - { - body, - headers, - retry: { limit: retries }, - throwHttpErrors: false, - followRedirect: followAllRedirects || followRedirects, - responseType: responseType || undefined, - }, - isUndefined, - ) - return await fn(`http://localhost:4000${route}`, xopts) -} - -export async function head(route, opts = { followRedirects: false }) { - const res = await get(route, { method: 'head', followRedirects: opts.followRedirects }) - return res -} - -export function post(route, opts) { - return get(route, Object.assign({}, opts, { method: 'post' })) -} - -const getDOMCache = new Map() - -export async function getDOMCached(route, options) { - // got() can take a `cache` option but it's slower than just doing - // a simple memoization pattern. - const key = `${route}::${JSON.stringify(options)}` - if (!getDOMCache.has(key)) { - getDOMCache.set(key, await getDOM(route, options)) - } - return getDOMCache.get(key) -} - -export async function getDOM( - route, - { headers, allow500s = false, allow404 = false, retries = 0 } = {}, -) { - const res = await get(route, { followRedirects: true, headers, retries }) - if (!allow500s && res.statusCode >= 500) { - throw new Error(`Server error (${res.statusCode}) on ${route}`) - } - if (!allow404 && res.statusCode === 404) { - throw new Error(`Page not found on ${route} (${res.statusCode})`) - } - const $ = cheerio.load(res.body || '', { xmlMode: true }) - $.res = Object.assign({}, res) - return $ -} - -// For use with the ?json query param -// e.g. await getJSON('/en?json=breadcrumbs') -export async function getJSON(route, opts) { - const res = await get(route, { ...opts, followRedirects: true }) - if (res.statusCode >= 500) { - throw new Error(`Server error (${res.statusCode}) on ${route}`) - } - if (res.statusCode >= 400) { - console.warn(`${res.statusCode} on ${route} and the response might not be JSON`) - } - return JSON.parse(res.body) -} diff --git a/src/tests/helpers/e2etest-ts.ts b/src/tests/helpers/e2etest.ts similarity index 87% rename from src/tests/helpers/e2etest-ts.ts rename to src/tests/helpers/e2etest.ts index 16b5dadde3f0..cec4a85226ee 100644 --- a/src/tests/helpers/e2etest-ts.ts +++ b/src/tests/helpers/e2etest.ts @@ -30,8 +30,11 @@ interface ResponseWithHeaders extends Response { headers: Record } +// Type alias for cached DOM results to improve maintainability +type CachedDOMResult = cheerio.Root & { res: Response; $: cheerio.Root } + // Cache to store DOM objects -const getDOMCache = new Map() +const getDOMCache = new Map() /** * Makes an HTTP request using the specified method and options. @@ -119,10 +122,10 @@ export function post( export async function getDOMCached( route: string, options: GetDOMOptions = {}, -): Promise { +): Promise { const key = `${route}::${JSON.stringify(options)}` if (!getDOMCache.has(key)) { - const { $ } = await getDOM(route, options) + const $ = await getDOM(route, options) getDOMCache.set(key, $) } // The non-null assertion is safe here because we've just set the key if it didn't exist @@ -134,12 +137,9 @@ export async function getDOMCached( * * @param route - The route to request. * @param options - Options for fetching the DOM. - * @returns A promise that resolves to the loaded DOM object. + * @returns A promise that resolves to the loaded DOM object with res attached and destructurable. */ -export async function getDOM( - route: string, - options: GetDOMOptions = {}, -): Promise<{ $: cheerio.Root; res: Response }> { +export async function getDOM(route: string, options: GetDOMOptions = {}): Promise { const { headers, allow500s = false, allow404 = false, retries = 0 } = options const res = await get(route, { followRedirects: true, headers, retries }) @@ -152,8 +152,13 @@ export async function getDOM( } const $ = cheerio.load(res.body || '', { xmlMode: true }) + const result = $ as CachedDOMResult + // Attach res to the cheerio object for backward compatibility + result.res = res + // Attach $ to itself for destructuring compatibility + result.$ = result - return { $, res } + return result } /**