Commit f39f585

Anton Medvedev <anton@medv.io>
2023-10-16 13:11:57
Update present-badges.ts to keep order of badges
1 parent 3e83e42
src/present-badges.ts
@@ -3,19 +3,7 @@ import { allBadges } from './all-badges/index.js'
 import path from 'node:path'
 import { fileURLToPath } from 'node:url'
 import { Data } from './collect/collect.js'
-
-export const mergeBadges = (...badges: (Badge | Badge[])[]): Badge[] =>
-  Object.values(
-    badges
-      .flat()
-      .reduce<Record<string, Badge>>(
-        (m, v) => Object.assign(m, { [v.id]: v }),
-        {},
-      ),
-  )
-
-const parseRegexp = (value: string): RegExp =>
-  new RegExp(`^${value}$`.replace('*', '.+'))
+import { parseMask } from './utils.js'
 
 export const presentBadges = (
   presenters: BadgePresenter[],
@@ -30,43 +18,59 @@ export const presentBadges = (
     const grant = badgeCollection(newBadges)
     presenter.present(data, grant)
 
+    if (newBadges.length === 0) {
+      continue
+    }
+
     // Enhance badges with image URLs.
     for (const b of newBadges) {
       const baseDir = path.basename(path.dirname(fileURLToPath(presenter.url)))
       b.image = `https://github.com/my-badges/my-badges/blob/master/src/all-badges/${baseDir}/${b.id}.png?raw=true`
     }
 
-    userBadges = mergeBadges(userBadges, newBadges)
-  }
+    const badgeFromPresenter = (x: Badge) =>
+      (presenter.badges as ID[]).includes(x.id)
 
-  if (compact) {
-    for (const presenter of presenters) {
-      if (!presenter.tiers) {
-        continue
-      }
-      const touchedBadges = userBadges.filter(({ id }) =>
-        (presenter.badges as ID[]).includes(id),
-      )
-      const newHighestTierBadge = touchedBadges.reduce(
-        (prev, curr) => (prev.tier > curr.tier ? prev : curr),
-        {} as Badge,
-      )
+    // Merge existing userBadges with newBadges.
+    if (compact && presenter.tiers) {
+      const newHighestTierBadge = newBadges.reduce((prev, curr) => {
+        return prev.tier > curr.tier ? prev : curr
+      })
 
-      omitBadges.push(
-        ...touchedBadges
-          .map(({ id }) => id)
-          .filter((id) => id !== newHighestTierBadge.id),
-      )
+      const existingBadgeIndex = userBadges.findIndex(badgeFromPresenter)
+      if (existingBadgeIndex === -1) {
+        userBadges.push(newHighestTierBadge)
+      } else if (
+        newHighestTierBadge.tier >= userBadges[existingBadgeIndex].tier
+      ) {
+        userBadges[existingBadgeIndex] = newHighestTierBadge
+
+        // Drop all other badges from the same presenter.
+        userBadges = userBadges.filter(
+          (x, i) => i === existingBadgeIndex || !badgeFromPresenter(x),
+        )
+      }
+    } else {
+      for (const badge of newBadges) {
+        const index = userBadges.findIndex((x) => x.id === badge.id)
+        if (index === -1) {
+          userBadges.push(badge)
+        } else {
+          userBadges[index] = badge
+        }
+      }
     }
   }
+
   if (pickBadges.length > 0) {
     userBadges = userBadges.filter((x) =>
-      pickBadges.map(parseRegexp).some((r) => r.test(x.id)),
+      pickBadges.map(parseMask).some((r) => r.test(x.id)),
     )
   }
+
   if (omitBadges.length > 0) {
     userBadges = userBadges.filter((x) =>
-      omitBadges.map(parseRegexp).every((r) => !r.test(x.id)),
+      omitBadges.map(parseMask).every((r) => !r.test(x.id)),
     )
   }
 
src/utils.ts
@@ -31,30 +31,6 @@ export function quoteAttr(s: string) {
 
 export const expectType = <T>(expression: T) => void 0
 
-export const decodeBase64 = (data: string) =>
-  Buffer.from(data, 'base64').toString('utf8')
-export const encodeBase64 = (data: string) =>
-  Buffer.from(data, 'utf8').toString('base64')
-
-export const upload = async (
-  octokit: Octokit,
-  route: Parameters<Octokit['request']>[0],
-  data: Parameters<Octokit['request']>[1],
-  dryrun?: boolean,
-) => {
-  if (dryrun) {
-    console.log(`Skipped pushing ${data?.path} (dryrun)`)
-    const filepath = path.join(process.cwd(), data?.path as string)
-
-    await fs.mkdir(path.dirname(filepath), { recursive: true })
-    await fs.writeFile(filepath, data?.content as string)
-
-    return
-  }
-
-  console.log(`Uploading ${data?.path}`)
-  return octokit.request(route, {
-    ...data,
-    content: encodeBase64(data?.content as string),
-  })
+export function parseMask(value: string): RegExp {
+  return new RegExp(`^${value}$`.replace('*', '.+'))
 }
test/get-data.test.ts
@@ -5,7 +5,6 @@ import path from 'node:path'
 import os from 'node:os'
 import { Octokit } from 'octokit'
 import { getData } from '../src/get-data.js'
-import { encodeBase64 } from '../src/utils.js'
 
 const tempy = () => fs.mkdtempSync(path.join(os.tmpdir(), 'tempy-'))
 
test/present-badges.test.ts
@@ -2,6 +2,7 @@ import * as assert from 'node:assert'
 import { describe, it } from 'node:test'
 import { Data } from '../src/collect/collect.js'
 import { presentBadges } from '../src/present-badges.js'
+import { Badge, BadgePresenter } from '../src/badges.js'
 
 describe('present-badges', () => {
   const data: Data = {
@@ -11,7 +12,7 @@ describe('present-badges', () => {
       },
     } as Data['user'],
     pulls: [] as Data['pulls'],
-    issues: {} as Data['issues'],
+    issues: [] as Data['issues'],
     repos: [
       {
         stargazers_count: 1000,
@@ -166,4 +167,46 @@ describe('present-badges', () => {
       },
     ])
   })
+
+  it('presentBadges() keeps existing order of badges', async () => {
+    const dumpPresenter: BadgePresenter = {
+      url: new URL('file:///tmp/dump.js'),
+      badges: ['a-commit', 'ab-commit', 'abc-commit'],
+      present: (_, grant) => {
+        grant('a-commit', 'a')
+        grant('ab-commit', 'ab')
+        grant('abc-commit', 'abc')
+      },
+    }
+
+    const oldUserBadges: Badge[] = [
+      {
+        id: 'a-commit',
+        tier: 0,
+        desc: 'a',
+        body: '',
+        image: '',
+      },
+      {
+        id: 'abc-commit',
+        tier: 0,
+        desc: 'abc',
+        body: '',
+        image: '',
+      },
+    ]
+
+    const userBadges = presentBadges(
+      [dumpPresenter],
+      data,
+      oldUserBadges,
+      [],
+      [],
+      false,
+    )
+    assert.deepEqual(
+      userBadges.map((x) => x.id),
+      ['a-commit', 'abc-commit', 'ab-commit'],
+    )
+  })
 })