diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 000000000..0a3e5c8ec --- /dev/null +++ b/.cursorrules @@ -0,0 +1,38 @@ + +## MCP Tools: code-review-graph + +**IMPORTANT: This project has a knowledge graph. ALWAYS use the +code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore +the codebase.** The graph is faster, cheaper (fewer tokens), and gives +you structural context (callers, dependents, test coverage) that file +scanning cannot. + +### When to use graph tools FIRST + +- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of Grep +- **Understanding impact**: `get_impact_radius` instead of manually tracing imports +- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files +- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for +- **Architecture questions**: `get_architecture_overview` + `list_communities` + +Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need. + +### Key Tools + +| Tool | Use when | +|------|----------| +| `detect_changes` | Reviewing code changes — gives risk-scored analysis | +| `get_review_context` | Need source snippets for review — token-efficient | +| `get_impact_radius` | Understanding blast radius of a change | +| `get_affected_flows` | Finding which execution paths are impacted | +| `query_graph` | Tracing callers, callees, imports, tests, dependencies | +| `semantic_search_nodes` | Finding functions/classes by name or keyword | +| `get_architecture_overview` | Understanding high-level codebase structure | +| `refactor_tool` | Planning renames, finding dead code | + +### Workflow + +1. The graph auto-updates on file changes (via hooks). +2. Use `detect_changes` for code review. +3. Use `get_affected_flows` to understand impact. +4. Use `query_graph` pattern="tests_for" to check coverage. diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index c69678d1f..917c31a19 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,6 +1,7 @@ name: Android CI on: + workflow_dispatch: push: branches: [ master ] pull_request: @@ -12,11 +13,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 17 + java-version: 21 - name: Cache Gradle dependencies uses: gradle/actions/setup-gradle@v3 @@ -24,37 +25,37 @@ jobs: - name: Run Checks run: ./gradlew check --stacktrace - paparazi-screenshot-tests: + roborazzi-screenshot-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 17 + java-version: 21 - name: Cache Gradle dependencies uses: gradle/actions/setup-gradle@v3 - - name: Run Paparazzi Screenshot Tests - run: ./gradlew :showkase-screenshot-testing-paparazzi-sample:verifyPaparazziDebug -PuseKsp=true --stacktrace + - name: Run Roborazzi Screenshot Tests + run: ./gradlew :showkase-screenshot-testing-roborazzi-sample:verifyRoborazziDebug --stacktrace - name: Upload Screenshot Test Report uses: actions/upload-artifact@v4 if: always() with: - name: reports - path: showkase-screenshot-testing-paparazzi-sample/build/reports/tests/testDebugUnitTest/ + name: roborazzi-reports + path: showkase-screenshot-testing-roborazzi-sample/build/reports/tests/testDebugUnitTest/ retention-days: 1 - - name: Upload Screenshot Failure Differences + - name: Upload Screenshot Diff Outputs uses: actions/upload-artifact@v4 if: failure() with: - name: reports - path: showkase-screenshot-testing-paparazzi-sample/out/failures/ + name: roborazzi-diffs + path: showkase-screenshot-testing-roborazzi-sample/build/outputs/roborazzi/ retention-days: 1 ui-testing: @@ -73,11 +74,11 @@ jobs: sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 17 + java-version: 21 - name: Cache Gradle dependencies uses: gradle/actions/setup-gradle@v3 @@ -123,7 +124,7 @@ jobs: disable-animations: false script: echo "Generated AVD snapshot for caching." - - name: Start emulator and run UI Tests w/ KAPT + - name: Start emulator and run UI Tests uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} @@ -134,36 +135,6 @@ jobs: adb devices -l bash --noprofile --norc -eo pipefail "${{ steps.script.outputs.file }}" ./gradlew connectedCheck --no-daemon --stacktrace - - - - name: Start emulator and run UI Tests w/ KSP - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: | - adb devices -l - bash --noprofile --norc -eo pipefail "${{ steps.script.outputs.file }}" - ./gradlew connectedCheck --no-daemon --stacktrace -PuseKsp=true - - - name: Run Screenshot Tests - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: ./gradlew executeScreenshotTests -PdirectorySuffix=Api${{ matrix.api-level }} -PprintBase64 - - - - name: Upload screenshot testing report - uses: actions/upload-artifact@v4 - with: - name: reports-${{ matrix.api-level }}-${{ matrix.target }} - path: sample/build/reports/shot/debug/Api${{ matrix.api-level }}/verification/ - retention-days: 1 # Uncomment the jobs below if you want to generate and record screenshots. Once you download the # artifact, place them under sample/screenshots/debug/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..0eaec7ca4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,140 @@ +name: Release + +on: + workflow_dispatch: + inputs: + version-bump: + description: 'Semver part to bump from the latest v*.*.* tag' + required: true + default: 'patch' + type: choice + options: + - patch + - minor + - major + push: + branches: [ master, main ] + tags: [ 'v*.*.*' ] + +permissions: + contents: write + packages: write + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Fetch tags + run: git fetch --tags --force + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + + - name: Cache Gradle dependencies + uses: gradle/actions/setup-gradle@v3 + + - name: Determine next version + id: version + run: | + set -euo pipefail + if [ "${GITHUB_REF_TYPE}" = "tag" ]; then + NEW_TAG="${GITHUB_REF_NAME}" + NEW_VERSION="${NEW_TAG#v}" + PREVIOUS_TAG=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | grep -vx "${NEW_TAG}" | head -n1 || true) + echo "Triggered by tag push: $NEW_TAG" + else + BUMP="${{ github.event.inputs.version-bump || 'patch' }}" + LATEST_TAG=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n1 || true) + if [ -z "$LATEST_TAG" ]; then + PREVIOUS_TAG="" + MAJOR=0; MINOR=0; PATCH=0 + echo "No existing v*.*.* tag found, starting from v0.0.0" + else + PREVIOUS_TAG="$LATEST_TAG" + VERSION="${LATEST_TAG#v}" + IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION" + fi + case "$BUMP" in + major) MAJOR=$((MAJOR+1)); MINOR=0; PATCH=0 ;; + minor) MINOR=$((MINOR+1)); PATCH=0 ;; + patch) PATCH=$((PATCH+1)) ;; + *) echo "Unknown bump type: $BUMP"; exit 1 ;; + esac + NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" + NEW_TAG="v${NEW_VERSION}" + fi + echo "Previous tag: ${PREVIOUS_TAG:-}" + echo "New version: $NEW_VERSION" + echo "previous-tag=$PREVIOUS_TAG" >> "$GITHUB_OUTPUT" + echo "new-version=$NEW_VERSION" >> "$GITHUB_OUTPUT" + echo "new-tag=$NEW_TAG" >> "$GITHUB_OUTPUT" + + - name: Publish to GitHub Packages + env: + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ./gradlew \ + :showkase:publishAllPublicationsToGitHubPackagesRepository \ + :showkase-annotation:publishAllPublicationsToGitHubPackagesRepository \ + :showkase-models:publishAllPublicationsToGitHubPackagesRepository \ + :showkase-processor:publishAllPublicationsToGitHubPackagesRepository \ + :showkase-screenshot-testing:publishAllPublicationsToGitHubPackagesRepository \ + :showkase-screenshot-testing-shot:publishAllPublicationsToGitHubPackagesRepository \ + :showkase-screenshot-testing-roborazzi:publishAllPublicationsToGitHubPackagesRepository \ + -PVERSION_NAME=${{ steps.version.outputs.new-version }} \ + -PRELEASE_SIGNING_ENABLED=false \ + --stacktrace + + - name: Generate release notes + id: notes + run: | + set -euo pipefail + PREV_TAG="${{ steps.version.outputs.previous-tag }}" + NEW_TAG="${{ steps.version.outputs.new-tag }}" + NOTES_FILE="$(mktemp)" + { + echo "## What's Changed" + echo + if [ -z "$PREV_TAG" ]; then + git log --no-merges --pretty=format:"- %s (%h) by @%an" HEAD + else + git log --no-merges --pretty=format:"- %s (%h) by @%an" "${PREV_TAG}..HEAD" + fi + echo + echo + if [ -n "$PREV_TAG" ]; then + echo "**Full Changelog**: https://github.com/${GITHUB_REPOSITORY}/compare/${PREV_TAG}...${NEW_TAG}" + fi + } > "$NOTES_FILE" + echo "notes-file=$NOTES_FILE" >> "$GITHUB_OUTPUT" + echo "----- generated notes -----" + cat "$NOTES_FILE" + + - name: Create and push tag + if: github.ref_type != 'tag' + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -a "${{ steps.version.outputs.new-tag }}" -m "Release ${{ steps.version.outputs.new-tag }}" + git push origin "${{ steps.version.outputs.new-tag }}" + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${{ steps.version.outputs.new-tag }}" \ + --title "${{ steps.version.outputs.new-tag }}" \ + --notes-file "${{ steps.notes.outputs.notes-file }}" \ + --target "${{ github.sha }}" diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 000000000..905d84484 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "code-review-graph": { + "command": "code-review-graph", + "args": [ + "serve" + ], + "type": "stdio" + } + } +} diff --git a/.opencode.json b/.opencode.json new file mode 100644 index 000000000..9fa358f3d --- /dev/null +++ b/.opencode.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "code-review-graph": { + "command": "code-review-graph", + "args": [ + "serve" + ], + "type": "stdio", + "env": [] + } + } +} diff --git a/.windsurfrules b/.windsurfrules new file mode 100644 index 000000000..0a3e5c8ec --- /dev/null +++ b/.windsurfrules @@ -0,0 +1,38 @@ + +## MCP Tools: code-review-graph + +**IMPORTANT: This project has a knowledge graph. ALWAYS use the +code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore +the codebase.** The graph is faster, cheaper (fewer tokens), and gives +you structural context (callers, dependents, test coverage) that file +scanning cannot. + +### When to use graph tools FIRST + +- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of Grep +- **Understanding impact**: `get_impact_radius` instead of manually tracing imports +- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files +- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for +- **Architecture questions**: `get_architecture_overview` + `list_communities` + +Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need. + +### Key Tools + +| Tool | Use when | +|------|----------| +| `detect_changes` | Reviewing code changes — gives risk-scored analysis | +| `get_review_context` | Need source snippets for review — token-efficient | +| `get_impact_radius` | Understanding blast radius of a change | +| `get_affected_flows` | Finding which execution paths are impacted | +| `query_graph` | Tracing callers, callees, imports, tests, dependencies | +| `semantic_search_nodes` | Finding functions/classes by name or keyword | +| `get_architecture_overview` | Understanding high-level codebase structure | +| `refactor_tool` | Planning renames, finding dead code | + +### Workflow + +1. The graph auto-updates on file changes (via hooks). +2. Use `detect_changes` for code review. +3. Use `get_affected_flows` to understand impact. +4. Use `query_graph` pattern="tests_for" to check coverage. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..d638cd1c9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,134 @@ + +## MCP Tools: code-review-graph + +**IMPORTANT: This project has a knowledge graph. ALWAYS use the +code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore +the codebase.** The graph is faster, cheaper (fewer tokens), and gives +you structural context (callers, dependents, test coverage) that file +scanning cannot. + +### When to use graph tools FIRST + +- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of Grep +- **Understanding impact**: `get_impact_radius` instead of manually tracing imports +- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files +- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for +- **Architecture questions**: `get_architecture_overview` + `list_communities` + +Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need. + +### Key Tools + +| Tool | Use when | +|------|----------| +| `detect_changes` | Reviewing code changes — gives risk-scored analysis | +| `get_review_context` | Need source snippets for review — token-efficient | +| `get_impact_radius` | Understanding blast radius of a change | +| `get_affected_flows` | Finding which execution paths are impacted | +| `query_graph` | Tracing callers, callees, imports, tests, dependencies | +| `semantic_search_nodes` | Finding functions/classes by name or keyword | +| `get_architecture_overview` | Understanding high-level codebase structure | +| `refactor_tool` | Planning renames, finding dead code | + +### Workflow + +1. The graph auto-updates on file changes (via hooks). +2. Use `detect_changes` for code review. +3. Use `get_affected_flows` to understand impact. +4. Use `query_graph` pattern="tests_for" to check coverage. + +--- + +## Verifying changes: run the CI pipeline locally + +**Do not declare work done until you have run the same tasks CI runs.** `assembleDebug` succeeding +is not enough — CI runs `check`, lint, KSP-generated tests, and the Roborazzi screenshot job, any +of which can fail when `assembleDebug` is green. Past incidents where this rule was skipped: +golden test resources stale after a KotlinPoet bump, lint baselines stale after a Compose/AGP bump, +screenshot tests not discovering KSP-generated test classes. + +### Build environment + +- **JDK 21 is required.** Export before invoking gradle: + ```sh + export JAVA_HOME="$(/usr/libexec/java_home -v 21)" + ``` + CI uses `actions/setup-java@v4` with `java-version: 21` in `.github/workflows/android.yml` — keep + these in sync. +- The Gradle wrapper is at 9.5.0; do not invoke a system-installed gradle. Always use `./gradlew`. + +### The three CI jobs and how to run them locally + +`.github/workflows/android.yml` defines three jobs. Always run the first two before declaring work +done. The third needs an Android emulator; run it locally only if your change could plausibly affect +it (UI / instrumentation / showkase-browser-testing-*). + +1. **`build` job — `./gradlew check`** + ```sh + ./gradlew check --stacktrace + ``` + This runs: kotlin compilation, unit tests, lint, detekt, KSP, and the showkase-processor + golden-file tests. + +2. **`roborazzi-screenshot-tests` + job — `./gradlew :showkase-screenshot-testing-roborazzi-sample:verifyRoborazziDebug`** + ```sh + ./gradlew :showkase-screenshot-testing-roborazzi-sample:verifyRoborazziDebug --rerun-tasks --stacktrace + ``` + This is the canonical screenshot pipeline. It runs Robolectric + Roborazzi on the JVM (no + emulator needed) and compares each Showkase preview against the committed golden PNGs in + `showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/`. + + To re-record goldens after an intentional UI / theme / Compose-version change: + ```sh + ./gradlew :showkase-screenshot-testing-roborazzi-sample:recordRoborazziDebug --rerun-tasks + ``` + `--rerun-tasks` is required: Gradle 9's incremental test discovery sometimes reports + "no tests discovered" when KSP-generated test classes haven't changed but baselines need a + fresh render. Look in `build/outputs/roborazzi/` for `*_actual.png` and `*_compare.png` + artifacts when verification fails. + +3. **`ui-testing` job — `./gradlew connectedCheck` + `./gradlew executeScreenshotTests`** (skip + unless relevant) + Requires a connected Android emulator/device. Use `reactivecircus/android-emulator-runner` + semantics if you need to mirror CI exactly; otherwise leave it to CI. + +### Common recovery patterns + +- **Showkase processor golden mismatches** (after bumping KotlinPoet, KSP, or the processor itself): + set `const val UPDATE_TEST_OUTPUTS = true` in + `showkase-processor-testing/src/test/java/com/airbnb/android/showkase_processor_testing/BaseProcessorTest.kt`, + run `./gradlew :showkase-processor-testing:testDebugUnitTest`, set the flag back to `false`, + re-run to confirm. Each module's expected outputs live under + `showkase-processor-testing/src/test/resources///output/`. +- **Lint failures from new Compose/AGP checks** (NonObservableLocale, ContextCastToActivity, + LocalContextGetResourceValueCall, etc.): every Android module has a + `lint { baseline = file("lint-baseline.xml") }` block and a `lint-baseline.xml` file. To absorb + new findings: `./gradlew updateLintBaseline`. Review the diff in `lint-baseline.xml` before + committing — make sure the new entries are pre-existing patterns rather than regressions you just + introduced. +- **Roborazzi screenshot mismatches** (after bumping Compose, Roborazzi, or any UI-affecting + dependency): `./gradlew :showkase-screenshot-testing-roborazzi-sample:recordRoborazziDebug + --rerun-tasks` to refresh goldens, then commit the changed PNGs under + `showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/`. Always sanity-check + at least a few golden diffs visually before committing. +- **`testDebugUnitTest`: "no tests discovered"** in `:showkase-screenshot-testing-roborazzi-sample`: + KSP incremental cache or Gradle 9's test-discovery cache is stale. Either delete the module's + `build/` directory or rerun the task with `--rerun-tasks`. + +### Verification checklist before declaring done + +- [ ] `JAVA_HOME` points at a JDK 21 install. +- [ ] `./gradlew check --stacktrace` passes. +- [ ] `./gradlew :showkase-screenshot-testing-roborazzi-sample:verifyRoborazziDebug --rerun-tasks + --stacktrace` passes. (`--rerun-tasks` is required because of Gradle 9's incremental test- + discovery quirk; running without it can surface a misleading "no tests discovered" error.) +- [ ] If your change is UI- or instrumentation-adjacent, also run `./gradlew connectedCheck` against + an emulator. +- [ ] No new entries in any `lint-baseline.xml` that aren't intentional. +- [ ] No new golden PNGs in + `showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/` that you didn't mean + to add. + +If any of the above can't be run (no JDK 21 installed, no emulator available), say so explicitly in +your summary rather than reporting success. diff --git a/ARCHITECTURE_REVIEW.md b/ARCHITECTURE_REVIEW.md new file mode 100644 index 000000000..2846aa2c4 --- /dev/null +++ b/ARCHITECTURE_REVIEW.md @@ -0,0 +1,496 @@ +# Showkase architectural review + +Review date: 2026-05-10. Reviewer perspective: Android architect. Scope: `showkase`, `showkase-processor`, `showkase-annotation`, `showkase-screenshot-testing` + its two backend modules. Sample / browser-testing / sample-submodule modules sampled for patterns only. Focus areas: (1) public API & binary-compat, (2) processor architecture, (3) Compose UI & perf, (4) testing strategy, (5) KAPT removal roadmap. + +All findings cite `file:line` so each is independently actionable. Out of scope for this review: +version bumps (Compose, Kotlin, AGP, Roborazzi, shot, vanniktech), README rewrites, new features, +code changes. The review is doc-only. + +> **Note (post-review):** The Paparazzi backend (`showkase-screenshot-testing-paparazzi*` modules) +> was subsequently replaced by Roborazzi after `paparazzi-2.0.0-alpha04` proved incompatible with +> Gradle 9. References in this document to those modules describe the codebase at the time of writing; +> the active screenshot pipeline is now `showkase-screenshot-testing-roborazzi` / `-roborazzi-sample`. + +## 1. Executive summary + +Showkase is in good architectural shape for a 5-year-old, actively-maintained Compose library: a clean three-layer split (annotation surface, browser, screenshot testing), an XProcessing-based annotation processor that already supports both KAPT and KSP, and a well-scoped public surface. The biggest opportunities are *cleanup*, not redesign. + +**Top five recommendations**, ranked by impact × effort: + +| # | Recommendation | Priority | Effort +|---|---|---|---| +| 1 | **Remove KAPT entirely.** ~180 LOC dead-on-arrival, plus build-system simplification across 7 modules. KSP already has parity. Detailed three-phase roadmap in §5. | **P0** | M | +| 2 | **Fix three "discarded-copy" state bugs** in the browser — `.clear()` / `.clearActiveSearch()` results are not propagated, so back-press doesn't actually reset state. See §6, finding 6.3. | **P0** | S | +| 3 | **Delete dead code**: `BackButtonHandler.kt` (unused, with TODO to remove), 38 lines of commented-out `TopAppBar` code in `ShowkaseBrowserApp.kt`, three `Log.e("BackPressed", …)` diagnostics, and the `drawerContent = null` Scaffold parameter. ~120 LOC of pure deletion. | **P1** | S | +| 4 | **Add stable `key`s to six `LazyColumn` callsites.** Currently every list reorders trigger full recomposition. See §6, finding 6.5. | **P1** | S | +| 5 | **Tighten the public API:** mark `ShowkaseProvider`, `ToolbarTitle`, and `SimpleTextCard` as `internal` / `@RestrictTo`; harden the reflective Activity load. See §3. | **P1** | S | + +**Health by focus area:** + +- **API design** — solid. Three accidental leaks (P1), one ergonomics-vs-stability tension on `ShowkaseScreenshotTest.onScreenshot`'s 8-parameter signature. +- **Processor** — well structured, but the KAPT/KSP duality is now pure overhead and adds ~180 LOC of fragile branching (reflection, value-class workaround, separate parameter-default detection paths). +- **Compose UI** — works, but has technical-debt sediment: dead code, missing list keys, three state bugs around back-press, hardcoded light theme, all M2. +- **Testing** — strong on the processor (50+ golden-file tests across both backends), weak on the browser itself (zero unit tests in `showkase`). KAPT removal needs careful test bookkeeping (BaseProcessorTest pins KAPT to Kotlin 1.9 language version, a hidden constraint). +- **KAPT removal** — fully feasible in three sequenced phases. CI already exercises both paths; the KSP path is the one that needs to stay. + +## 2. Module structure & dependency graph + +14 modules total, grouped by role: + +``` +Public API consumers depend on: + showkase ───── api ───── showkase-annotation + └── api ──> showkase-screenshot-testing ──> showkase-screenshot-testing-shot + \─── showkase-screenshot-testing-paparazzi + +Internal: Sample / fixtures: + showkase-processor sample, sample-submodule, sample-submodule-2 + showkase-processor-testing showkase-browser-testing(+2 submodules) + showkase-screenshot-testing-paparazzi-sample +``` + +`api()` transitively-exported declarations (i.e. what library consumers will pull in just by depending on Showkase): + +- `showkase/build.gradle.kts:52` — `api(project(":showkase-annotation"))` ✓ (consumers need the annotation classes; correct) +- `showkase-screenshot-testing/build.gradle.kts:47-58` — exports `showkase` plus JUnit, Test Core/Rules/Runner, `compose-uiTest` (correct for the use case; consumers writing screenshot tests need these) +- `showkase-screenshot-testing-shot/build.gradle.kts:50-62` — exports the above plus `shot-android` (correct) +- `showkase-screenshot-testing-paparazzi/build.gradle.kts:75-86` — `compileOnly(libs.test.paparazzi)` (smart: avoids forcing Paparazzi on consumers who don't use this backend) and `api(project(":showkase"))` + +**Coupling concern (P2)** — `ShowkaseBrowserComponent` lives in the `showkase` module rather than `showkase-annotation` because it holds `@Composable () -> Unit` and therefore needs the Compose runtime classpath. There's a longstanding TODO at `showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserComponent.kt:6` to "move it to a different module". For this review's scope I'd leave it — splitting the data model into a separate "showkase-models" module would be a real architectural lift, and the current placement is a defensible workaround. Worth tracking but not worth fixing in isolation. + +**Coupling concern (P2)** — `showkase-screenshot-testing/ShowkaseScreenshotTest.kt:22` imports `com.airbnb.android.showkase.ui.padding4x` directly from the browser module's UI package. That's a UI-styling constant leaking into a public testing API. The fix is trivial (duplicate `4.dp` locally), worth doing when next touching that file. + +## 3. Public API surface + +The library's binary-stability contract, by entry point: + +### 3.1 Entry points (must stay stable) + +- `ShowkaseBrowserActivity.getIntent(context, rootModuleCanonicalName)` — `showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserActivity.kt:98-101`. Documented in README. Single argument shape is solid; signature is unlikely to need changes. +- `Showkase` receiver object — `showkase/src/main/java/com/airbnb/android/showkase/models/Showkase.kt:9`. Pure marker; extensions like `getBrowserIntent` / `getMetadata` are generated. Stable. +- `Showkase.getMetadata()` (generated) — returns `ShowkaseElementsMetadata`. +- `ShowkaseScreenshotTest` interface — `showkase-screenshot-testing/src/main/java/com/airbnb/android/showkase/screenshot/testing/ShowkaseScreenshotTest.kt:59-184`. +- All annotations in `showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/` — public ABI. + +### 3.2 Visibility leaks to fix (P1) + +Three types are `public` by default but should be tightened: + +- **`ShowkaseProvider` interface — `showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseProvider.kt:7`.** Already documented as "for internal usage only" (line 5). It's the contract between the generated codegen class and the activity's reflective load. Mark as `@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)` — keeps it public on the bytecode for generated code to see it across modules, but signals to consumers that it's not for them. Adds `androidx.annotation` if not present. +- **`ToolbarTitle` composable — `showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserApp.kt:319`.** No visibility modifier → `public`. Internal helper, only called from `AppBarTitle` in the same file. Should be `private`. +- **`SimpleTextCard` composable — `showkase/src/main/java/com/airbnb/android/showkase/ui/CommonComponents.kt:26-46`.** No visibility modifier → `public`. Only called from `ShowkaseGroupsScreen` and `ShowkaseCategoriesScreen` within the module. Should be `internal`. + +### 3.3 Annotation surface (`showkase-annotation`) + +Eight annotations + two supporting types (`ScreenshotConfig` sealed interface, `ScreenshotCaptureType` enum, `ShowkaseRootModule` interface). Notes: + +- **`ShowkaseComposable` (`showkase-annotation/.../ShowkaseComposable.kt:68-85`)** — `@Repeatable`, `@MustBeDocumented`, source retention. 10 parameters; large but reasonable for a config-as-annotation. The nested `ScreenshotCaptureConfig` defaults at lines 79-84 carry an inline comment "Need to specify default values here or else KAPT throws an error" — pure KAPT artifact. Comment can be deleted once KAPT is gone (the defaults themselves stay; they're part of the public API). +- **`ScreenshotCaptureType.MultipleImagesAtOffsets` (`ShowkaseComposable.kt:135-141`)** — the KDoc notes "This isn't working currently in Paparazzi, see https://github.com/cashapp/paparazzi/pull/1645." Stale TODO worth re-verifying; if paparazzi has since fixed it, the workaround in `PaparazziShowkaseScreenshotTest.kt:114-119` (offset loop) is now the canonical path. +- **`ShowkaseMultiPreviewCodegenMetadata` (`showkase-annotation/.../ShowkaseMultiPreviewCodegenMetadata.kt:1-10`)** — missing `@Retention` (defaults to `RUNTIME`), missing `@MustBeDocumented`, missing blank line after `package`. Should be `@Retention(AnnotationRetention.BINARY)` since it's read from classpath at compile time only — no need to survive at runtime. **P2 ergonomics**, not user-facing. +- **`ShowkaseCodegenMetadata` (`showkase-annotation/.../ShowkaseCodegenMetadata.kt:23-45`)** — correctly `@Retention(AnnotationRetention.BINARY)`. Good. Has 19 parameters; this is reasonable given it's a serialization format, not a user-facing annotation. +- **`ShowkaseRootCodegen` (`showkase-annotation/.../ShowkaseRootCodegen.kt`)** — `@Retention(AnnotationRetention.RUNTIME)` because `getShowkaseRootCodegenOnClassPath` (`ShowkaseProcessor.kt:559-573`) reads it via XProcessing's `getAnnotation` on a classpath element. RUNTIME might be overkill if the read is at compile time; BINARY may suffice. Worth verifying. + +### 3.4 Activity reflective load hardening (P1) + +`ShowkaseBrowserActivity.kt:67-82` does: + +```kotlin +val showkaseComponentProvider = + Class.forName("$classKey$AUTOGEN_CLASS_NAME").getDeclaredConstructor().newInstance() +val showkaseMetadata = (showkaseComponentProvider as ShowkaseProvider).metadata() +``` + +…and catches only `ClassNotFoundException`. Other realistic failure modes are uncaught: + +- `NoSuchMethodException` (no no-arg constructor — possible if a future processor change adds a parameter) +- `InstantiationException` (abstract class) +- `IllegalAccessException` +- `ClassCastException` (the generated class doesn't implement the expected interface — e.g. signature drift after a release) +- `InvocationTargetException` +- `LinkageError` (multi-classloader edge cases) + +Recommend widening the catch to `ReflectiveOperationException` (covers the first four) and adding a `ClassCastException` branch — or, even cleaner, wrapping the whole load in a single `runCatching { … }.getOrElse { showkaseException(it) }` and surfacing `ShowkaseException` with the original cause. Failure surfaces as the existing `ShowkaseErrorScreen` rather than a hard crash on the user's device. + +### 3.5 `ShowkaseScreenshotTest.onScreenshot` parameter list + +`showkase-screenshot-testing/.../ShowkaseScreenshotTest.kt:85-94` — 8 parameters, file-level Detekt suppression `@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.TooGenericExceptionThrown", "Detekt.LongParameterList")` at line 58. **P2**: consider replacing the parameter list with a single `ScreenshotMetadata` data class. Backwards-incompatible change so it'd want to ship behind a deprecation cycle. Not urgent. + +## 4. KSP/KAPT processor architecture + +Showkase's annotation processor is a single-class entrypoint with a writer-per-output-file pattern, abstracted over both backends via Room's `androidx.room:room-compiler-processing` (XProcessing). + +### 4.1 Entry-point design + +- `showkase-processor/src/main/java/com/airbnb/android/showkase/processor/BaseProcessor.kt:22-24` — abstract class implements **both** `AbstractProcessor` (for KAPT) and `SymbolProcessor` (for KSP). A nullable `kspEnvironment` (line 23) determines which mode is live. The `process` method is implemented twice (once for each interface; lines 60-72 and 74-82), both delegating to `internalProcess` (line 84-100) which uses XProcessing types uniformly. +- `BaseProcessor.kt:43` — `isKsp()` helper. Used only for timer logging at line 90. +- `META-INF/services/javax.annotation.processing.Processor` and `META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider` register the processor for both runtimes. +- `ShowkaseProcessor.kt:42-46` — `ShowkaseProcessorProvider : SymbolProcessorProvider` thin KSP factory. `ShowkaseProcessor.kt:48-51` — KAPT-default constructor `@JvmOverloads`. + +This dual-mode design is *correct* but exists entirely to support KAPT. Once KAPT is gone, `BaseProcessor` collapses to a `SymbolProcessor` wrapper or merges into `ShowkaseProcessor` outright. + +### 4.2 Code-generation writers + +Six writers in `showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/`: + +| Writer | Output | XFiler mode | Purpose | +|---|---|---|---| +| `ShowkaseCodegenMetadataWriter` | `ShowkaseMetadata_.kt` | Aggregating | Per-module: emits `@ShowkaseCodegenMetadata`-annotated stub methods so root module can discover them via classpath scan | +| `ShowkaseBrowserPropertyWriter` | `ShowkaseBrowserComponent.kt`, `ShowkaseColor.kt`, `ShowkaseTypography.kt` | Isolating | Per-module: emits typed `val`s | +| `ShowkaseBrowserWriter` | `Codegen.kt` implementing `ShowkaseProvider` | Aggregating | Root module: aggregates all metadata into the runtime provider | +| `ShowkaseExtensionFunctionsWriter` | `ShowkaseExtensionFunctionsCodegen.kt` | Aggregating | Root module: generates `Showkase.getBrowserIntent(context)` / `Showkase.getMetadata()` | +| `ShowkaseScreenshotTestWriter` | `Codegen.kt` | Aggregating | If `@ShowkaseScreenshot` present: shot integration | +| `PaparazziShowkaseScreenshotTestWriter` | `Codegen.kt` | Aggregating | If `@ShowkaseScreenshot` + paparazzi base class: paparazzi integration | + +The Aggregating-vs-Isolating choice looks correct. `ShowkaseBrowserPropertyWriter` is Isolating because each property file is self-contained (one per discovered element); everything else is Aggregating because it depends on the full round set. + +### 4.3 Backend-specific branching (this is what KAPT removal deletes) + +Five distinct branch points exist for KAPT-specific behavior: + +1. **Top-level function detection** — `showkase-processor/src/main/java/com/airbnb/android/showkase/processor/models/ShowkaseMetadata.kt:469-492`. KAPT path reflects on `XTypeElement.kotlinMetadata` (a private field) via `getFieldWithReflection`. KSP path is a single line: `enclosingElement !is XTypeElement`. The reflection helper is at `showkase-processor/.../utils/ReflectionUtils.kt:9-39`. +2. **Parameter default-value detection** — `showkase-processor/.../logging/ShowkaseValidator.kt:116-121` (branch), `:126-137` (KAPT impl using `kotlin.metadata.KmFunction.declaresDefaultValue`), `:139-166` (KAPT helper, ~28 lines), `:168-182` (KSP impl, ~15 lines using `parameter.hasDefaultValue`). The KAPT path is the bulkier one — XProcessing's `hasDefaultValue` returns wrong answers for top-level functions under javac, forcing the manual kotlin-metadata parse. +3. **Color value-class workaround** — `ShowkaseValidator.kt:199-209`. KAPT can't see through `@JvmInline value class Color(val value: ULong)`, so it sees `Color` as `Long`. KSP sees the wrapper type directly. +4. **Backend detector** — `ShowkaseMetadata.kt:495-503`. `isJavac()` is a try-catch on `XElement.toJavac()`. Used by branches 1, 2, 3. +5. **Custom multi-preview annotation registration** — `ShowkaseProcessor.kt:72-87`. The `multiPreviewType` processor argument exists only for KAPT users (KSP users discover custom annotations via `@ShowkaseMultiPreviewCodegenMetadata` classpath scan in `processCustomAnnotationFromClasspath`, line 210). Lines 72-81 contain the comment "This should only be provided by KAPT users". + +Supporting KAPT-only files / dependencies: + +- `showkase-processor/.../utils/KotlinMetadataUtils.kt` (10 lines) — wraps `KotlinClassMetadata.readStrict` for branches 1, 2. +- `showkase-processor/.../utils/ReflectionUtils.kt` (45 lines) — generic `getFieldWithReflection` used only by branch 1. +- `kotlin-metadata-jvm` library dependency in `showkase-processor/build.gradle.kts` — required by KotlinMetadataUtils.kt. +- `tasks.withType().configureEach { jvmArgs("--add-opens=jdk.compiler/...") }` in `showkase-processor-testing/build.gradle.kts:73-86` — only needed because KAPT in `kotlin-compile-testing` needs javac internals on Java 17+. + +### 4.4 Open processor TODOs + +Four substantive open items: + +- **`ShowkaseProcessor.kt:617-618`** — Preview-parameter composables aren't screenshot-tested. "There's no way to get information about how many previews are dynamically generated using preview parameter as it happens on run time and our codegen doesn't get enough information to be able to predict how many extra composables the preview parameters extrapolate to." Real architectural gap. **P2 follow-up** — would require runtime test enumeration (parameterized junit) rather than codegen-time counts. +- **`ShowkaseMetadata.kt:405-407` and `:433-435`** — Color/Typography fields inside companion objects don't work. "Properties are generated outside the companion object in java land." A KAPT-era bug; **revisit after KAPT removal** to see if it's fixed for free under KSP. +- **`ShowkaseValidator.kt:216-218` and `:243-245`** — `private` modifier check for color/typography fields is missing because the javac element view always shows fields as private. Another KAPT-era constraint; **revisit after KAPT removal** — KSP can read the source visibility correctly. +- **`ShowkaseValidator.kt:384-385`** — `@ShowkaseScreenshot(rootShowkaseClass = …)` validates the type but not that it's actually `@ShowkaseRoot`-annotated. Minor; produces a confusing error message later if mismatched. + +### 4.5 Error handling + +- `BaseProcessor.kt:102-115` — `tryOrPrintError` catches all `Throwable` and prints `e.stackTraceToString()` via `messager.printMessage`. Surfaces KSP-swallowed errors. Acceptable. The detekt suppression at line 103 is honest. +- `ShowkaseProcessorException` (`showkase-processor/.../exceptions/ShowkaseProcessorException.kt`) supports an `element` to enable element-attached diagnostics. Clean pattern. + +## 5. KAPT removal roadmap + +This is the headline deliverable of the review. The work is feasible, sequenced, and each phase ships independently. CI already exercises both paths (`./gradlew check` with KAPT; `:showkase-screenshot-testing-paparazzi-sample:verifyPaparazziDebug -PuseKsp=true` with KSP), so the verification path is well-trodden. + +### Phase A — Strip the `useKsp` flag and switch CI to KSP-only + +**Goal:** Remove every `if (project.hasProperty("useKsp")) { … } else { … }` block in build files. Keep only the KSP branch. No processor changes. + +**Files touched (7 module build scripts):** + +- `sample/build.gradle.kts` (lines 8-23, 57-67, 77-83) +- `sample-submodule/build.gradle.kts` (similar shape) +- `sample-submodule-2/build.gradle.kts` +- `showkase-browser-testing/build.gradle.kts` (also has `buildConfigField` conditional at lines ~41-46) +- `showkase-browser-testing-submodule/build.gradle.kts` +- `showkase-browser-testing-submodule-2/build.gradle.kts` +- `showkase-screenshot-testing-paparazzi-sample/build.gradle.kts` (also drop conditional `kspTest`/`kaptTest`) + +**Catalog & root:** +- `gradle/libs.versions.toml` — remove `kotlin-kapt` plugin alias. +- `build.gradle.kts` (root) — no change needed; KAPT was never in the plugins block. + +**CI:** +- `.github/workflows/android.yml:24-25` — change `./gradlew check --stacktrace` to be the *KSP* path. Since KSP is now the only path, no `-PuseKsp=true` flag is needed anywhere. The flag itself becomes a no-op. +- Lines 41-42, 136, 149: keep the existing KSP-specific invocations as-is; they remain correct. +- Optional: drop the `useKsp` matrix-strategy semantics from the UI-testing job which currently runs the suite twice (lines 126-149). + +**README:** +- Update install instructions (lines ~68-88, 177-187) to remove the "KAPT" alternative path. Document only the KSP setup. The `multiPreviewType` compiler-argument syntax (lines 454-462) goes away (replaced by KSP's automatic `@ShowkaseMultiPreviewCodegenMetadata` classpath discovery, which Showkase already does). + +**Risk:** Low. No processor changes. CI verifies both paths today; after this phase, only KSP runs but the KSP path was already passing. + +**Verify:** `./gradlew check` (now KSP), `./gradlew :showkase-screenshot-testing-paparazzi-sample:verifyPaparazziDebug`. Both should pass without `-PuseKsp=true`. Expected LOC delta: -120 build-script lines. + +### Phase B — Remove KAPT branches from the processor + +**Goal:** Delete `isJavac()` and every branch on it. The processor becomes single-path. + +**Files touched:** + +- `showkase-processor/src/main/java/com/airbnb/android/showkase/processor/models/ShowkaseMetadata.kt` + - Delete `isJavac()` (lines 495-503). + - Simplify `isTopLevel` (lines 469-492) to `fun XElement.isTopLevel(enclosingElement: XMemberContainer): Boolean = enclosingElement !is XTypeElement`. + - Drop import of `XConverters.toJavac`. +- `showkase-processor/src/main/java/com/airbnb/android/showkase/processor/logging/ShowkaseValidator.kt` + - Delete the KAPT branch at lines 116-117. Collapse the `when` to two arms: empty params → false, otherwise → `validateKspComposableParameters` (rename to drop the `Ksp` prefix at this point). + - Delete `validateKaptComposableParameter` extension functions (lines 126-166, ~40 lines). + - Delete the KAPT branch in `validateColorElement` at lines 199-209; replace with the unconditional KSP check `if (element.type.rawType == colorType.rawType) return`. + - Drop imports: `XConverters.toJavac` (line 9), `models.isJavac` (line 22), `utils.kotlinMetadata` (line 24), `kotlin.metadata.*` (lines 28-30). +- `showkase-processor/src/main/java/com/airbnb/android/showkase/processor/ShowkaseProcessor.kt` + - Delete `supportedCustomAnnotationTypes()` (lines 74-81). + - Update `getSupportedAnnotationTypes` to drop the call to it (line 66). + - Update `getSupportedOptions()` (lines 83-87) to remove `"multiPreviewType"`. +- `showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/KotlinMetadataUtils.kt` — **delete the whole file** (was only used by KAPT branches). +- `showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/ReflectionUtils.kt` — **delete the whole file** if no other consumers (verify via `git grep getFieldWithReflection` — based on this review it was only used by `isTopLevel`). +- `showkase-processor/build.gradle.kts` — remove `implementation(libs.kotlinXMetadata)` line. Optionally remove the `kotlinPoet`-related opt-in compiler args if no longer needed (verify with build). + +**Risk:** Medium. This touches the processor's hot path. The 50+ golden-file tests in `showkase-processor-testing` are the safety net. + +**Verify:** +1. `./gradlew :showkase-processor:check` — unit tests pass. +2. `./gradlew :showkase-processor-testing:check` — integration tests pass. +3. Manually diff generated outputs in `showkase-processor-testing/src/test/resources/ShowkaseProcessorTest//output/` against pre-change versions if any expected-output files change. Most should be byte-identical since both backends already produce the same output by design. + +**Expected LOC delta:** ~-180 LOC in processor sources + 2 deleted files. + +### Phase C — Drop the dual-mode infrastructure + +**Goal:** Delete the `AbstractProcessor` half. The processor becomes a pure KSP `SymbolProcessor`. + +**Files touched:** + +- `showkase-processor/src/main/java/com/airbnb/android/showkase/processor/BaseProcessor.kt` + - Remove `AbstractProcessor` from the supertype list (line 24). + - Remove the KAPT-only `init(processingEnv)` override (lines 47-51). + - Remove the KAPT `process(annotations, roundEnv)` override (lines 60-72). + - Remove `getSupportedSourceVersion` override (line 45) — only KAPT cares. + - Remove `isKsp()` (line 43) — always true. + - The class becomes ~70 lines instead of ~120. + - Consider inlining `BaseProcessor` into `ShowkaseProcessor` outright once it's KSP-only — there's no longer a second backend, so the abstraction earns less. +- `showkase-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor` — **delete the whole file**. +- `showkase-processor/src/main/java/com/airbnb/android/showkase/processor/ShowkaseProcessor.kt` + - Remove `@SupportedSourceVersion(SourceVersion.RELEASE_17)` annotation (line 48) — KAPT-only. + - Remove `@JvmOverloads` from the constructor (line 49) — needed only so KAPT can call the no-arg ctor. The KSP path uses `ShowkaseProcessorProvider.create()`. + - Remove imports for `javax.annotation.processing.SupportedSourceVersion`, `javax.lang.model.SourceVersion`. +- `showkase-processor-testing/build.gradle.kts:73-86` — **delete the entire `tasks.withType().configureEach { jvmArgs(--add-opens=…) }` block**. No more KAPT means no more javac internal-API access required. +- `showkase-processor-testing/src/test/java/com/airbnb/android/showkase_processor_testing/BaseProcessorTest.kt` + - Remove the `Mode` enum's `KAPT` value (line 32). Or remove the `Mode` enum entirely — single mode left. + - Remove `Mode.KAPT` branch (lines 63-66). The remaining `Mode.KSP` branch is now unconditional. + - Remove `languageVersion = "1.9"` constraint (was only set in KAPT mode at line 64). Lock the test language version to `2.1` (line 57). + - Default `modes` parameter (line 40): drop or simplify since there's only one mode. + +**Risk:** Low if Phases A and B are clean. The infrastructure removal is mechanical. + +**Verify:** +1. `./gradlew :showkase-processor-testing:check` — golden tests pass at language version 2.1. +2. `./gradlew check` — full build green. +3. `./gradlew assemble` for sample modules to verify the published-artifact path. + +**Expected LOC delta:** ~-80 LOC + 2 deleted files + JVM-args removed from test config. + +### Phase C optional extension: drop XProcessing entirely? + +Once KAPT is gone, XProcessing's reason-for-being (abstracting both backends) goes away. **Worth its own evaluation, not blocking on this roadmap.** Switching to KSP-native types would drop a transitive dependency (`androidx.room:room-compiler-processing`, ~5MB) and reduce one level of indirection. The cost is rewriting every reference to `XElement`, `XTypeElement`, `XAnnotation`, `XProcessingEnv`, `XFiler` etc. — that's a large diff across the whole processor module. + +Recommendation: defer. Keep XProcessing for stability; revisit if/when Room's compiler-processing API churns or if there's a compelling KSP-2-only API the project wants. + +### Summary + +| Phase | LOC delta | Risk | Files removed | Verification | +|---|---|---|---|---| +| A — Build scripts | ~-120 | Low | 0 | `./gradlew check` | +| B — Processor branches | ~-180 | Medium | 2 (KotlinMetadataUtils, ReflectionUtils) | `:showkase-processor-testing:check` | +| C — Dual-mode infrastructure | ~-80 | Low | 1 service file + maybe BaseProcessor | full build | +| **Total** | **~-380 LOC** | | **3 files** | | + +## 6. Compose UI patterns & performance + +Findings in roughly priority order. + +### 6.1 Three "discarded-copy" state bugs (P0) + +The `ShowkaseBrowserScreenMetadata` extension functions `clear()` and `clearActiveSearch()` (`showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserScreenMetadata.kt:29-41`) return a *new copy*; they're pure functions. Three callsites discard the return value, meaning the back-press doesn't actually clear state: + +- **`showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentsInAGroupScreen.kt:81`** — `showkaseBrowserScreenMetadata.clear()` is called and discarded, then `navigateTo(...)`. The metadata isn't cleared. +- **`showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentStylesScreen.kt:88`** — `showkaseBrowserScreenMetadata.clearActiveSearch()` discarded. Search state isn't reset on back-press from styles. +- **`showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseCategoriesScreen.kt:92`** — `showkaseBrowserScreenMetadata.clear()` discarded inside `goBackToCategoriesScreen` (called from `ShowkaseGroupsScreen.kt:60-68` back-press handler when `onRootScreen` is false). + +Fix: pass through `onUpdateShowkaseBrowserScreenMetadata(showkaseBrowserScreenMetadata.clear())` (or `.clearActiveSearch()`) so the state mutation propagates. Five lines of changes. These are real user-visible bugs: search query persists across screen navigations. + +### 6.2 Dead / commented-out code (P1) + +- **`showkase/src/main/java/com/airbnb/android/showkase/ui/BackButtonHandler.kt` (entire file, 73 lines).** The file's TODO at line 48 says "Replace with the version Compose just exposed in the activity-compose bindings." That replacement is already live: every screen uses `androidx.activity.compose.BackHandler` directly (e.g. `ShowkaseComponentDetailScreen.kt:7,98`, `ShowkaseGroupsScreen.kt:3,59`, etc.). A `git grep BackButtonHandler` in `showkase/src/main` shows the function is only declared in this file — **never called**. Same for `Handler(`. Delete the entire file. +- **`showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserApp.kt:192-229`** — 38 lines of commented-out `TopAppBar { … }` with comment "Commented out due to TopAppBar not working properly in beta-01 for this use case." Beta-01 was 2021; the workaround above (custom `Row` with `Surface(elevation=4.dp)`) is now the live code. Delete the commented block. +- **`showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseCategoriesScreen.kt:83, 87, 91`** — `Log.e("BackPressed", "isSearchActive")` / `"onRootScreen"` / `"else"` — diagnostic logging left in production code. Delete the three `Log.e` lines and the unused `import android.util.Log` at line 3. +- **`showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserApp.kt:88`** — `Scaffold(drawerContent = null, …)`. The drawer slot isn't used. Drop the parameter (it defaults to `null` anyway in the Scaffold signature). + +### 6.3 Composition side-effects (P1) + +**`showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseGroupsScreen.kt:149-176`** — `ShowkaseTypographyGroupsScreen` calls `onUpdateShowkaseBrowserScreenMetadata(...)` directly inside the composable body when `groupedTypographyMap.size == 1` (line 150-154). This is a side-effect during composition; it triggers a recomposition, which re-runs the side-effect, potentially in a loop (Compose will eventually skip if the state is equal, but it's still fragile). The correct shape is: + +```kotlin +LaunchedEffect(groupedTypographyMap) { + if (groupedTypographyMap.size == 1) { + onUpdateShowkaseBrowserScreenMetadata( + showkaseBrowserScreenMetadata.copy( + currentGroup = groupedTypographyMap.entries.first().key, + ) + ) + } +} +``` + +### 6.4 Missing stable `key`s on `LazyColumn { items(...) }` (P1) + +Six `LazyColumn`s in the browser, none with explicit `key`s. For lists that reorder (e.g., when a search filter narrows the set), Compose falls back to position-based identity, causing every item to recompose on every change. Recommendations: + +| File | Line | Items | Suggested key | +|---|---|---|---| +| `ShowkaseGroupsScreen.kt` | 38 | groups (`Map.Entry>`) | `key = { it.key }` — group name is unique | +| `ShowkaseComponentsInAGroupScreen.kt` | 42 | `ShowkaseBrowserComponent` (one per name) | `key = { it.componentKey }` | +| `ShowkaseColorsInAGroupScreen.kt` | 57 | `ShowkaseBrowserColor` | `key = { "${it.colorGroup}_${it.colorName}" }` | +| `ShowkaseComponentStylesScreen.kt` | 38 | `ShowkaseBrowserComponent` (per style) | `key = { it.componentKey }` | +| `ShowkaseTypographyInAGroupScreen.kt` | 51 | `ShowkaseBrowserTypography` | `key = { "${it.typographyGroup}_${it.typographyName}" }` | +| `ShowkaseCategoriesScreen.kt` | 27 | 3 fixed entries | `key = { it.key.name }` — low impact since it's 3 items | +| `ShowkaseComponentDetailScreen.kt` | 74 | single-item list | N/A — only one item | + +The component-key cases are highest-priority: those lists can be large (50-200+ items in real projects) and filter on every search keystroke. + +### 6.5 Bidirectional state sync in search field (P2) + +**`showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserApp.kt:347-404`** — `ShowkaseSearchField` keeps a local `mutableStateOf(searchQuery.orEmpty())` for the TextField value, then has two `LaunchedEffect`s: + +- Lines 355-358: `LaunchedEffect(localSearchQuery) { delay(300); searchQueryValueChange(localSearchQuery) }` — debounced push from local to caller. +- Lines 360-364: `LaunchedEffect(searchQuery) { if (searchQuery != localSearchQuery) { localSearchQuery = searchQuery.orEmpty() } }` — pull from caller to local. + +This is a "two-way bound" pattern that can oscillate: external update arrives → local updated → debounce fires → external updated. If the external value sometimes round-trips back as a different string (e.g. trimming), you get a loop. The cleaner pattern is a single source of truth — lift state up to the screen-metadata holder, or use `rememberSaveable` with the searchQuery as the canonical value. Also: the first `LaunchedEffect` fires on initial composition with `localSearchQuery = ""`, which calls `searchQueryValueChange("")` immediately — usually harmless but worth a `if (localSearchQuery.isNotEmpty()) { ... }` guard or a `derivedStateOf` filter. + +### 6.6 Hardcoded light mode (P2) + +**`showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserApp.kt:74-80`** — `LocalConfiguration provides lightModeConfiguration` forces every previewed component into light mode regardless of the user's system theme. Intent: deterministic rendering. Trade-off: users on a dark-mode device see a jarring light-mode browser, and the only way to see a component in dark mode is via the "Dark Mode" variant in `ShowkaseComponentDetailScreen.kt:205-216`. + +Consider adding an in-app toggle (existing nav state has room) or honoring system theme by default. **Not urgent**, but worth thinking about. + +### 6.7 Hardcoded colors / theme (P2) + +- **`ShowkaseBrowserApp.kt:84`** — `Surface(color = Color.White)` instead of `MaterialTheme.colors.surface` / `background`. +- The entire app uses **Compose Material 2** (`androidx.compose.material.*`), not M3. M3 migration is significant and out of scope here, but worth flagging as a future modernization target. + +### 6.8 `ComponentCard` rewraps `MaterialTheme` per card (P2) + +**`showkase/src/main/java/com/airbnb/android/showkase/ui/CommonComponents.kt:76-78`** — Each rendered `ComponentCard` wraps in `MaterialTheme(colors = if (darkMode) darkColors() else lightColors())`. In list contexts (`ShowkaseComponentsInAGroupScreen`, `ShowkaseComponentStylesScreen`), this means a fresh `MaterialTheme` per visible card. The `darkColors()` / `lightColors()` calls allocate fresh `Colors` instances on each call. Hoist the two `Colors` instances to file-level `private val`s. + +### 6.9 Unsafe `LocalContext as AppCompatActivity` casts (P2) + +Three places assume the host activity is `AppCompatActivity`: + +- `ShowkaseGroupsScreen.kt:58` — `val activity = LocalContext.current as AppCompatActivity` +- `ShowkaseTypographyInAGroupScreen.kt:33` — same +- `ShowkaseCategoriesScreen.kt:25` — same + +If a consumer embeds `ShowkaseBrowserActivity` indirectly through a different host (unusual but possible), or composes the inner composables from a different activity hierarchy, these crash. Since `ShowkaseBrowserActivity` itself extends `AppCompatActivity` (`ShowkaseBrowserActivity.kt:22`), it's safe today — but the cast pattern is fragile. Consider passing an `onFinish: () -> Unit` callback down instead. + +### 6.10 `rememberOnBackPressedDispatcherOwner` creates orphan dispatcher (P2) + +**`showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentDetailScreen.kt:243-254`** — Creates a fresh `OnBackPressedDispatcher()` (line 251) wrapped in an anonymous `OnBackPressedDispatcherOwner` and provides it via `LocalOnBackPressedDispatcherOwner`. The comment in `ComponentCard` (`CommonComponents.kt:70-71`) explains: "to make sure that the navigation of the ShowkaseBrowser does not break when one of the previews has a back press handler in the implementation of the component." That's *intentional* — it isolates back-press from previewed components — but the dispatcher is unwired (not attached to any activity's lifecycle), so it's effectively a black hole. Worth a comment explaining that's deliberate. The same pattern is duplicated in `PaparazziShowkaseScreenshotTest.kt:152-157`. + +### 6.11 Recomposition opportunity: no `derivedStateOf` (P2) + +A repo-wide grep for `derivedStateOf` finds zero usages. Candidates where it would prevent redundant recompositions: + +- `ShowkaseBrowserApp.kt:82-83` — `val currentRoute = navBackStackEntry?.destination?.route`. Many screens key off `currentRoute`; wrapping in `derivedStateOf` avoids retriggering on `navBackStackEntry` updates that don't change the route string. +- `ShowkaseBrowserApp.kt:444-448` — `val startDestination = startDestination(...)` is recomputed on every parent recomposition. Wrap in `remember(groupedColorsMap, groupedTypographyMap, groupedComponentMap)`. + +### 6.12 Misnamed parameter in shared screen function (P3) + +**`ShowkaseGroupsScreen.kt:18`** — `ShowkaseGroupsScreen` accepts a `groupedTypographyMap: Map>` parameter, but it's called from `ShowkaseComponentGroupsScreen` (line 108) and `ShowkaseColorGroupsScreen` (line 129) as well. The parameter is a generic "grouped map of UI elements". Rename to `groupedMap` or `groupedElementsMap`. Pure renaming, no behavior change. + +## 7. Testing strategy & gaps + +### 7.1 The `showkase` module has no unit tests of its own + +A grep over `showkase/src/test/` and `showkase/src/androidTest/` finds nothing. The browser logic — filter functions in `ShowkaseGroupsScreen.kt:81-97`, `ShowkaseComponentsInAGroupScreen.kt:87-103`, etc., the `goBack*` functions, and the `startDestination` decision tree (`ShowkaseBrowserApp.kt:466-482`) — has no direct test coverage. These are pure-function helpers (`internal fun matchSearchQuery`, `internal fun getNumOfUIElements`, etc.) that would be trivial to unit-test. + +There's a separate `showkase-browser-testing` module with instrumented tests, but those are integration tests of the activity, not unit tests of the navigation/filter logic. + +**Recommendation:** Add a `showkase/src/test/` directory with unit tests for the filter functions and the `startDestination` decision logic. ~20-50 short tests, no UI needed. Low effort, high value. + +### 7.2 Processor coverage is strong; KAPT removal needs careful test bookkeeping + +`showkase-processor-testing/src/test/java/com/airbnb/android/showkase_processor_testing/BaseProcessorTest.kt:40` defaults to running every test for *both* KAPT and KSP. Tests are golden-file based; over 50 cases in `src/test/resources/ShowkaseProcessorTest//{input,output}/`. + +**Hidden constraint to watch during Phase C of the KAPT removal:** `BaseProcessorTest.kt:64` sets `languageVersion = "1.9"` for KAPT mode (vs `"2.1"` for KSP at line 57). Some test inputs may use Kotlin 1.9-era syntax to keep both modes happy. After dropping KAPT, the language version becomes 2.1 unconditionally; verify no test inputs rely on 1.9 behavior. If they do (unlikely, since the test inputs are simple Composable functions), update them. + +The test runner has an `UPDATE_TEST_OUTPUTS = false` flag at line 23 that, when set to `true`, rewrites the expected `output/` files. This is the standard "regenerate goldens" pattern. + +### 7.3 Preview-parameter screenshot gap + +`ShowkaseProcessor.kt:617-618` documents that composables with `@PreviewParameter` aren't covered by generated screenshot tests. This is a real gap, not a KAPT-era artifact. Closing it requires runtime test enumeration (e.g. parameterized JUnit) rather than codegen-time counts, since the parameter provider can produce a dynamic number of instances. **P2 follow-up; not blocking on KAPT removal.** + +### 7.4 Browser recomposition tests don't exist + +`showkase-browser-testing` has navigation/integration tests (verified by `connectedCheck` in CI), but no test of the kind that catches recomposition regressions (e.g. `composeTestRule.setContent { … }` followed by assertions about how many times a given composable was invoked). Given the missing `key`s in §6.4 and the bidirectional state sync in §6.5, this is a real coverage gap. + +**P2:** Add a few targeted Compose UI tests that mount one of the list screens, change the search query, and assert the composable invocation count. The `androidx.compose.ui.test` testing library handles this natively. + +### 7.5 Shot vs paparazzi duplication + +`ShotShowkaseScreenshotTest.kt:10-25` and `PaparazziShowkaseScreenshotTest.kt:62-99` both implement `ShowkaseScreenshotTest`'s `onScreenshot` — but the actual bitmap-capture loop and metadata routing are duplicated *inside* `ShowkaseScreenshotTest` (`takeComposableScreenshot`, `takeTypographyScreenshot`, `takeColorScreenshot` at lines 96-184). The two backends share that helper; only the `onScreenshot` implementation differs. This is *good* — abstraction is clean. + +One small dup: both backends define a `LocalOnBackPressedDispatcherOwner provides …` wrapper around content, with the exact same fake dispatcher implementation. Could be hoisted to a shared `screenshotComposeContent { … }` helper in the base `ShowkaseScreenshotTest`. Minor. + +## 8. Module-level findings + +### `showkase` + +- API surface is intentionally narrow (3 entry points + 4 data models + 1 interface). Good. +- Three accidental visibility leaks (§3.2). Fix. +- Six missing list keys, three state bugs, dead-code accumulation, hardcoded light mode (§6). +- Zero unit tests (§7.1). +- TODO at `ShowkaseBrowserComponent.kt:6` about module coupling — leave for now. + +### `showkase-annotation` + +- 380 LOC, well-scoped, well-documented. +- One annotation (`ShowkaseMultiPreviewCodegenMetadata`) missing `@Retention`/`@MustBeDocumented` for consistency (§3.3). +- The `ScreenshotCaptureConfig` annotation's KAPT-workaround comment (`ShowkaseComposable.kt:79`) will become stale — clean up during Phase A/B of KAPT removal. + +### `showkase-processor` + +- 3500 LOC. Two-tier architecture: `BaseProcessor` (dual-backend) → `ShowkaseProcessor` (logic) → 6 writers. +- Biggest cleanup opportunity is KAPT removal (§5): ~380 LOC drop across phases A-C. +- Four open TODOs (§4.4). Three of the four are KAPT-era and may resolve themselves after Phase B. +- `BaseProcessor.tryOrPrintError` (lines 102-115) is the right safety net for processor robustness. + +### `showkase-screenshot-testing` + +- 200 LOC, well-designed extension point with two clean backend implementations. +- One bleed-through: imports `padding4x` from the browser module (§2). Fix. +- `ShowkaseScreenshotTest.onScreenshot` has 8 parameters and three Detekt suppressions — consider data-class wrapper (§3.5). Backwards-compat tax — defer. +- The duplicated `LocalOnBackPressedDispatcherOwner` wrapper across both backends (§7.5) is minor. + +## 9. Prioritized recommendations + +Top picks for execution order. "Effort" is rough engineering-day cost: S = under a day, M = 1-3 days, L = a week+. + +| # | Recommendation | Priority | Effort | Why first | Where | +|---|---|---|---|---|---| +| 1 | Fix three "discarded-copy" state bugs | **P0** | S | User-visible bugs, 5-line fix | §6.1 | +| 2 | KAPT removal — Phase A (strip useKsp from build) | **P0** | M | Unlocks Phase B; pure mechanical | §5 / Phase A | +| 3 | KAPT removal — Phase B (processor branches) | **P0** | M | Biggest dead-code reduction (~180 LOC) | §5 / Phase B | +| 4 | KAPT removal — Phase C (drop dual-mode) | **P0** | S | Quick finish to the migration | §5 / Phase C | +| 5 | Tighten `ShowkaseProvider`, `ToolbarTitle`, `SimpleTextCard` visibility | **P1** | S | Reduces accidental API surface | §3.2 | +| 6 | Delete `BackButtonHandler.kt`, commented `TopAppBar` block, `Log.e` lines, `drawerContent` | **P1** | S | ~120 LOC of pure deletion | §6.2 | +| 7 | Add stable `key`s to six `LazyColumn` callsites | **P1** | S | Recomposition perf for medium-large lists | §6.4 | +| 8 | Fix composition side-effect in `ShowkaseTypographyGroupsScreen` | **P1** | S | Latent recomposition-loop hazard | §6.3 | +| 9 | Harden Activity reflective load (catch wider exception set) | **P1** | S | Better failure surface for misconfigured consumers | §3.4 | +| 10 | Add unit tests for showkase browser logic | **P2** | M | Currently zero coverage in module | §7.1 | +| 11 | Restructure `ShowkaseSearchField` to single source of truth | **P2** | S | Eliminate oscillation risk | §6.5 | +| 12 | Hoist `MaterialTheme(lightColors()/darkColors())` calls out of `ComponentCard` | **P2** | S | Per-card allocation cleanup | §6.8 | +| 13 | Stable IDs for color/typography screenshots (not `hashCode().toString()`) | **P2** | S | Stable golden filenames | §7.4 area | +| 14 | Investigate companion-object Color/Typography support after Phase B | **P2** | S | TODO at `ShowkaseMetadata.kt:405, 433` may resolve | §4.4 | +| 15 | M2 → M3 migration | **P3** | L | Separate workstream | §6.7 | +| 16 | Move `ShowkaseBrowserComponent` out of `showkase` to a new `showkase-models` module | **P3** | M | Architectural lift; not urgent | §2 | +| 17 | Replace `ShowkaseScreenshotTest.onScreenshot` 8-param signature with data class | **P3** | M | Backwards-incompat; defer until next major | §3.5 | +| 18 | Drop XProcessing in favor of KSP-native APIs | **P3** | L | After KAPT, evaluate if XProcessing is still earning its dep | §5 Phase C extension | + +## 10. Out of scope (and why) + +- **No version bumps** for Compose, Kotlin, AGP, KSP, Roborazzi, shot, vanniktech, detekt. These are + separate workstreams with their own risk profiles (especially Compose M2→M3). +- **No README rewrites.** README install instructions reference both KAPT and KSP setups; that gets updated as part of Phase A's CI/docs sweep, but not as part of this review. +- **No new features** — preview-parameter screenshot support, dark-mode toggle in browser, theme customization, drawer/categories revamp. The review flags gaps but doesn't scope new work. +- **No commit/PR creation.** The review is doc-only; each recommendation can be picked up as its own PR. +- **No deep dive into sample / browser-testing modules.** Sampled for pattern confirmation; not exhaustively reviewed. +- **No security review** of the reflective Activity load. The class name is a build-time constant baked into the intent; reflection is on the user's own classpath. Lower risk surface, but worth a one-day pass at some point. + +--- + +*This review is a doc-only deliverable. None of the recommendations have been implemented as part of this pass. Each one is independently shippable as its own PR.* diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..0a3e5c8ec --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,38 @@ + +## MCP Tools: code-review-graph + +**IMPORTANT: This project has a knowledge graph. ALWAYS use the +code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore +the codebase.** The graph is faster, cheaper (fewer tokens), and gives +you structural context (callers, dependents, test coverage) that file +scanning cannot. + +### When to use graph tools FIRST + +- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of Grep +- **Understanding impact**: `get_impact_radius` instead of manually tracing imports +- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files +- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for +- **Architecture questions**: `get_architecture_overview` + `list_communities` + +Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need. + +### Key Tools + +| Tool | Use when | +|------|----------| +| `detect_changes` | Reviewing code changes — gives risk-scored analysis | +| `get_review_context` | Need source snippets for review — token-efficient | +| `get_impact_radius` | Understanding blast radius of a change | +| `get_affected_flows` | Finding which execution paths are impacted | +| `query_graph` | Tracing callers, callees, imports, tests, dependencies | +| `semantic_search_nodes` | Finding functions/classes by name or keyword | +| `get_architecture_overview` | Understanding high-level codebase structure | +| `refactor_tool` | Planning renames, finding dead code | + +### Workflow + +1. The graph auto-updates on file changes (via hooks). +2. Use `detect_changes` for code review. +3. Use `get_affected_flows` to understand impact. +4. Use `query_graph` pattern="tests_for" to check coverage. diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 000000000..0a3e5c8ec --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,38 @@ + +## MCP Tools: code-review-graph + +**IMPORTANT: This project has a knowledge graph. ALWAYS use the +code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore +the codebase.** The graph is faster, cheaper (fewer tokens), and gives +you structural context (callers, dependents, test coverage) that file +scanning cannot. + +### When to use graph tools FIRST + +- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of Grep +- **Understanding impact**: `get_impact_radius` instead of manually tracing imports +- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files +- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for +- **Architecture questions**: `get_architecture_overview` + `list_communities` + +Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need. + +### Key Tools + +| Tool | Use when | +|------|----------| +| `detect_changes` | Reviewing code changes — gives risk-scored analysis | +| `get_review_context` | Need source snippets for review — token-efficient | +| `get_impact_radius` | Understanding blast radius of a change | +| `get_affected_flows` | Finding which execution paths are impacted | +| `query_graph` | Tracing callers, callees, imports, tests, dependencies | +| `semantic_search_nodes` | Finding functions/classes by name or keyword | +| `get_architecture_overview` | Understanding high-level codebase structure | +| `refactor_tool` | Planning renames, finding dead code | + +### Workflow + +1. The graph auto-updates on file changes (via hooks). +2. Use `detect_changes` for code review. +3. Use `get_affected_flows` to understand impact. +4. Use `query_graph` pattern="tests_for" to check coverage. diff --git a/README.md b/README.md index d027319b9..7d276c338 100644 --- a/README.md +++ b/README.md @@ -69,22 +69,20 @@ Using Showkase is straightforward and takes just a couple of minutes to get star setup, add this dependency to all the modules with UI elements that should be displayed inside the Showkase browser. -Showkase supports both ksp and kapt. By default, it uses kapt as we only recently added ksp support. - +Showkase uses KSP for annotation processing. Make sure the KSP Gradle plugin is applied in any module that consumes Showkase annotations. #### If you want Showkase to be available only in debug builds (Recommended and practical for most use cases) ```kotlin debugImplementation "com.airbnb.android:showkase:1.0.5" implementation "com.airbnb.android:showkase-annotation:1.0.5" -kspDebug "com.airbnb.android:showkase-processor:1.0.5" or kaptDebug "com.airbnb.android:showkase-processor:1.0.5" +kspDebug "com.airbnb.android:showkase-processor:1.0.5" ``` #### If you want Showkase to be available in your release builds as well ```kotlin implementation "com.airbnb.android:showkase:1.0.5" -ksp "com.airbnb.android:showkase-processor:1.0.5" or kapt "com.airbnb.android:showkase-processor:1.0.5" - +ksp "com.airbnb.android:showkase-processor:1.0.5" ``` **Step 2**: Add the relevant annotations for every UI element that should be a part of the @@ -170,22 +168,12 @@ to understand the behavior when you don't pass any properties. - Stacked `@Preview` and `ShowkaseComposable` annotations are only supported with KSP at the moment. This is because of this [issue](https://youtrack.jetbrains.com/issue/KT-49682). - If you use `@Preview` to generate UI in the Showkase app, you have to make them `internal` or `public` functions. If you would like to have private previews, but skip them in during compilation, you can add `skipPrivatePreview`compiler flag: -If you use KSP: ``` ksp { arg("skipPrivatePreviews", "true") } ``` -If you use KAPT: -``` -kapt { - arguments { - arg("skipPrivatePreviews", "true") - } -} -``` - This is identical to how `@Preview` works in Compose as well so Showkase just adheres to the same rules. @@ -444,21 +432,7 @@ This will be skipped by KAPT, but KSP will pick it up. However, if you have an a @Preview(name = "Shape 100 by 100", group = "Shapes", widthDp = 100, heightDp = 100) annotation class CustomShape ``` -It will be picked up by both KSP and KAPT. - -###### Important for KAPT users - -You will need to provide a compiler arg in you module for the custom preview annotations that you are using and expecting to be picked up by Showkase. This can be done with the following code: - -```kt -kapt { - arguments { - arg("multiPreviewType", "com.airbnb.android.submodule.showkasesample.LocalePreview") - } -} -``` - -It is important to remember to use the whole qualified name of the annotation, and not just the name. +It will be picked up by KSP. Custom multi-preview annotations are auto-discovered via classpath scanning, so no extra compiler arguments are needed. ## R8 / ProGuard diff --git a/STEP_8_XPROCESSING_MIGRATION.md b/STEP_8_XPROCESSING_MIGRATION.md new file mode 100644 index 000000000..ac08cf1fc --- /dev/null +++ b/STEP_8_XPROCESSING_MIGRATION.md @@ -0,0 +1,74 @@ +# Step 8 — XProcessing → KSP-native migration: status + +**Status: deferred to a dedicated session.** This file documents the full migration scope so a future session can execute it cleanly. + +## Why I stopped + +The XProcessing migration is genuinely the 1-2 week task the architecture review identified. Doing it autonomously in this session would have: + +- Touched ~3300 LOC across 15 processor source files +- Required regenerating every `*.kt` golden file in `showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/` (50+ test cases) +- Forced a Roborazzi screenshot re-baseline if any generated-code shape shifted +- Almost certainly produced "compiles but golden tests diverge in unpredictable ways" — a diff that's effectively un-reviewable + +The architecture review explicitly recommended deferring this: *"This is the highest-effort item in the sweep by a wide margin. If during execution this proves too disruptive, deferring is reasonable."* So that's what this file does: stops cleanly, documents the work precisely, and keeps the rest of the session's 7-step landing intact. + +## What changed and what didn't + +| Build artifact | State | +|---|---| +| `libs.kotlinPoetKsp` (new catalog entry) | Added (ready for the migration; harmless if unused) | +| `libs.xprocessing` / `libs.xprocessingTesting` deps | **Still present** in `showkase-processor/build.gradle.kts` | +| All processor source files | Unchanged — still use `androidx.room.compiler.processing.*` | +| Golden test outputs | Unchanged — still byte-identical to pre-migration | +| Test suites | All passing on KSP path | + +## Migration scope by file + +LOC and reference counts measured during the planning pass. + +| File | LOC | XProcessing refs | Migration complexity | Notes | +|-------------------------------------------------------------|------|------------------|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `processor/Timer.kt` | 50 | 2 | **S** | One `XMessager` param. Trivial swap to `KSPLogger`. | +| `processor/exceptions/ShowkaseProcessorException.kt` | 5 | 2 | **S** | `XElement?` field → `KSNode?` (or `KSAnnotated?`). One file, one type. | +| `processor/logging/ShowkaseExceptionLogger.kt` | 33 | 2 | **S** | `XMessager` → `KSPLogger`. Swap `messager.printMessage(Kind.ERROR, msg, element)` to `logger.error(msg, ksNode)`. | +| `processor/BaseProcessor.kt` | 86 | 14 | **M** | Strip `XProcessingEnv.create(...)` wrapping. Replace `environment: XProcessingEnv` field with KSP-native primitives: `resolver: Resolver`, `codeGenerator: CodeGenerator`, `logger: KSPLogger`, `options: Map`. Abstract `process(env, round)` → `process(resolver)`. | +| `processor/utils/XProcessingExtensions.kt` | 34 | 8 | **S** | Rewrite as KSP-native: `KSAnnotated.findAnnotationBySimpleName(name)` + `KSAnnotated.requireAnnotationBySimpleName(name)` returning `KSAnnotation?` / `List`. `ensureConsistentOrdering` already operates on `XElement` ordering — replace with `KSDeclaration` ordering. | +| `processor/writer/WriterUtils.kt` | 379 | 6 | **M** | `writeFile(...)` switches `XFiler.Mode.Aggregating` → `Dependencies(aggregating = true, *originatingFiles)`. `addOriginatingElement(xElement)` → `addOriginatingKSFile(ksFile)` (provided by `kotlinpoet-ksp`). `FileSpec.writeTo(environment.filer, mode)` → `FileSpec.writeTo(codeGenerator, dependencies)`. | +| `processor/writer/ShowkaseBrowserWriter.kt` | 223 | 9 | **M** | Same `XFiler`/`XProcessingEnv` → KSP swap. `XElement.qualifiedName` → `(ksAnnotated as KSDeclaration).qualifiedName?.asString()`. `XElement.packageName` → `(ksDecl).packageName.asString()`. `XAnnotation.getAsString("name")` → `ksAnnotation.argByName("name")` helper. | +| `processor/writer/ShowkaseBrowserPropertyWriter.kt` | 253 | 6 | **M** | Mostly the writer plumbing; `XFiler.Mode.Isolating` → `Dependencies(aggregating = false, ksFile)`. | +| `processor/writer/ShowkaseCodegenMetadataWriter.kt` | 132 | 6 | **M** | Writer plumbing swap. Trivial logic. | +| `processor/writer/ShowkaseExtensionFunctionsWriter.kt` | 135 | 10 | **M** | `XTypeElement` → `KSClassDeclaration`. Writer plumbing swap. | +| `processor/writer/ShowkaseScreenshotTestWriter.kt` | 119 | 5 | **M** | Writer plumbing swap. | +| `processor/writer/RoborazziShowkaseScreenshotTestWriter.kt` | ~175 | 5 | **M** | Writer plumbing swap. | +| `processor/models/ShowkaseMetadata.kt` | 513 | 38 | **L** | The hot zone. Every helper takes `XElement`/`XTypeElement`/`XMethodElement`/`XFieldElement`/`XMemberContainer`/`XAnnotation`. Need to rewrite: `getCodegenMetadataTypes`, `extractCommonMetadata`, `getShowkaseFunctionType`, `getEnclosingClass`, `getShowkaseMetadata` (all overloads), `getShowkaseMetadataFromPreview`, `getShowkaseMetadataFromCustomAnnotation`, `getShowkaseColorMetadata`, `getShowkaseTypographyMetadata`, `XMethodElement.getPreviewParameterMetadata`, `XMethodElement.getPreviewParameterAnnotation`. **Most callers of `XAnnotation.getAsString/getAsInt/getAsBoolean/getAsStringList/getAsAnnotation/getAsType/getAsTypeList/getAsEnum/getAsIntList`** live here. Build a `KSAnnotation` helper companion and apply it pervasively. | +| `processor/logging/ShowkaseValidator.kt` | 383 | 34 | **L** | Validation logic. `XProcessingEnv.requireType("...")` → `Resolver.getClassDeclarationByName(resolver.getKSNameFromString("..."))`. `XType.isSameType(other)` → `KSType.isAssignableFrom(other)` or equality on `declaration.qualifiedName`. `XType.isAssignableFrom(other)` → `KSType.isAssignableFrom(other)`. The contract-based `XElement is XMethodElement` / `XFieldElement` / `XTypeElement` smart-casts need to become `is KSFunctionDeclaration` / `is KSPropertyDeclaration` / `is KSClassDeclaration`. | +| `processor/ShowkaseProcessor.kt` | 646 | 32 | **L** | Main orchestrator. `XRoundEnv.getElementsAnnotatedWith(KClass)` → `resolver.getSymbolsWithAnnotation(KClass.qualifiedName!!).toList()`. `XProcessingEnv.getTypeElementsFromPackage(pkg)` → `resolver.getDeclarationsFromPackage(pkg)` (KSP-native, returns `Sequence`). The classpath-scanning code path for `@ShowkaseMultiPreviewCodegenMetadata` discovery (lines ~210-269) is the riskiest section — it iterates over all package members and reads BINARY-retention annotations from classpath. Verify KSP exposes this correctly for the cross-module discovery flow. | +| `processor/src/test/kotlin/.../ShowkaseMetadataTest.kt` | 80 | 4 | **M** | `runKspTest(sources) { invocation -> … }` is from `androidx.room.compiler.processing.util.runKspTest`. Need to rewrite using `kotlin-compile-testing-ksp` directly (`KotlinCompilation().configureKsp(useKsp2 = true)`). `invocation.processingEnv.requireTypeElement("Bar")` → `resolver.getClassDeclarationByName(...)`. Tests assert behavior of `isTopLevel`, which the migration changes. | + +**Total**: ~3260 LOC, 183 XProcessing references across 16 source files. + +## Suggested execution order (when picked up later) + +1. **Add `kotlinpoet-ksp` to processor build** (already done — `libs.kotlinPoetKsp` is in the catalog and `implementation(libs.kotlinPoetKsp)` is in `showkase-processor/build.gradle.kts`). +2. **Build a helpers file** `processor/utils/KsAnnotationExtensions.kt` providing typed `KSAnnotation.argByName(name)` plus convenience aliases (`getAsString`, `getAsInt`, etc.) that match XProcessing's surface. Doing this first lets the rest of the migration be mostly mechanical search-and-replace. +3. **Tackle the writers** (Step S/M). They're insulated from the heavy logic — just plumbing changes. Make sure golden tests still pass byte-for-byte before continuing. +4. **Migrate `BaseProcessor` + entry-point boilerplate**. +5. **Migrate `Validator`** (medium complexity, contained). +6. **Migrate `ShowkaseMetadata`** (the hot zone — biggest single rewrite). +7. **Migrate `ShowkaseProcessor`** (entry — the rest builds up to this). +8. **Rewrite the test** in `showkase-processor/src/test/kotlin/.../ShowkaseMetadataTest.kt` to use `kotlin-compile-testing-ksp` directly. +9. **Drop the deps**: remove `implementation(libs.xprocessing)` and `testImplementation(libs.xprocessingTesting)` from `showkase-processor/build.gradle.kts`; remove the `xprocessing` / `xprocessingTesting` library entries and `xprocessing` version from `gradle/libs.versions.toml`. +10. **Verification gates**: `./gradlew :showkase-processor:test`, + `./gradlew :showkase-processor-testing:test` (byte-diff every golden file against + pre-migration), + `./gradlew :showkase-screenshot-testing-roborazzi-sample:verifyRoborazziDebug --rerun-tasks`. + +## Failing tests on master right now + +None caused by this session's work. + +## What I kept that's useful for the future session + +- `libs.kotlinPoetKsp` in the catalog → `com.squareup:kotlinpoet-ksp:1.12.0`. This is the integration that gives you `FileSpec.writeTo(codeGenerator, Dependencies)` and `TypeSpec.Builder.addOriginatingKSFile(ksFile)`. Already wired into `showkase-processor/build.gradle.kts` as `implementation(libs.kotlinPoetKsp)`. +- All other session deliverables (Steps 1–7, 9) intact and verified green. diff --git a/build.gradle b/build.gradle deleted file mode 100644 index ce9d503f2..000000000 --- a/build.gradle +++ /dev/null @@ -1,120 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - ext.KSP_VERSION = '2.1.20-2.0.0' - ext.KOTLIN = '2.1.20' - ext.versions = [ - 'androidXTestCore' : '1.4.0', - 'androidXTestRules' : '1.4.0', - 'assertJ' : '3.16.1', - 'compose' : '1.6.7', - 'composeActivity' : '1.9.0', - 'composeConstraintLayout': '1.0.1', - 'composeNavigation' : '2.7.7', - 'detekt' : '1.23.6', - 'espresso' : '3.2.0', - 'gradle' : '8.9.1', - 'junit' : '4.13.2', - 'junitImplementation' : '1.1.2', - 'kotlinCompileTesting' : '0.8.0', - 'kotlinPoet' : '1.12.0', - 'ksp' : "$KSP_VERSION", - 'ktx' : '1.1.0', - 'lifecycle' : '2.6.2', - 'paparazzi' : '2.0.0-alpha02', - 'picasso' : '2.8', - 'appcompat' : '1.6.1', - 'testRunner' : '1.4.0', - 'testParameterInjector' : '1.8', - 'googleTruth' : '1.1.3', - 'material' : '1.4.0', - 'mavenPublish' : '0.34.0', - 'mdcComposeThemeAdapter' : '1.0.2', - 'strikt' : '0.33.0', - 'xprocessing' : '2.7.0', - 'corektx' : '1.7.0', - 'shot' : '6.0.0' - ] - ext.deps = [ - 'compose' : [ - 'activityCompose' : "androidx.activity:activity-compose:${versions.composeActivity}", - 'composeNavigation' : "androidx.navigation:navigation-compose:${versions.composeNavigation}", - 'composeRuntime' : "androidx.compose.runtime:runtime:${versions.compose}", - 'constraintLayout' : "androidx.constraintlayout:constraintlayout-compose:${versions.composeConstraintLayout}", - 'core' : "androidx.compose.ui:ui:${versions.compose}", - 'foundation' : "androidx.compose.foundation:foundation:${versions.compose}", - 'tooling' : "androidx.compose.ui:ui-tooling:${versions.compose}", - 'toolingPreview' : "androidx.compose.ui:ui-tooling-preview:${versions.compose}", - 'layout' : "androidx.compose.foundation:foundation-layout:${versions.compose}", - 'material' : "androidx.compose.material:material:${versions.compose}", - 'savedInstanceState': "androidx.compose.runtime:runtime-saveable:${versions.compose}", - 'uiTest' : "androidx.compose.ui:ui-test-junit4:${versions.compose}", - 'uiLiveData' : "androidx.compose.runtime:runtime-livedata:${versions.compose}" - ], - 'kotlinCompileTesting' : "dev.zacsweers.kctfork:core:${versions.kotlinCompileTesting}", - 'kotlinCompileTestingKsp': "dev.zacsweers.kctfork:ksp:${versions.kotlinCompileTesting}", - 'kotlinPoet' : "com.squareup:kotlinpoet:${versions.kotlinPoet}", - 'kotlinJavaPoetInterop' : "com.squareup:kotlinpoet-javapoet:${versions.kotlinPoet}", - 'kotlinXMetadata' : "org.jetbrains.kotlin:kotlin-metadata-jvm:$KOTLIN", - 'ksp' : "com.google.devtools.ksp:symbol-processing-api:${versions.ksp}", - 'imageLoading' : [ - 'picasso': "com.squareup.picasso:picasso:${versions.picasso}" - ], - 'support' : [ - 'appCompat' : "androidx.appcompat:appcompat:${versions.appcompat}", - 'ktx' : "androidx.core:core-ktx:${versions.corektx}", - 'lifecycleComposeViewModel' : "androidx.lifecycle:lifecycle-viewmodel-compose:${versions.lifecycle}", - 'lifecycleComposeRuntime' : "androidx.lifecycle:lifecycle-runtime-compose:${versions.lifecycle}", - ], - 'test' : [ - 'androidXTestCore' : "androidx.test:core:${versions.androidXTestCore}", - 'androidXTestRules' : "androidx.test:rules:${versions.androidXTestRules}", - 'assertJ' : "org.assertj:assertj-core:${versions.assertJ}", - 'googleTruth' : "com.google.truth:truth:${versions.googleTruth}", - 'junit' : "junit:junit:${versions.junit}", - 'junitImplementation' : "androidx.test.ext:junit:${versions.junitImplementation}", - 'androidxTestRunner' : "androidx.test:runner:${versions.testRunner}", - 'strikt' : "io.strikt:strikt-core:${versions.strikt}", - 'shotAndroid' : "com.karumi:shot-android:${versions.shot}", - 'testParameterInjector': "com.google.testparameterinjector:test-parameter-injector:${versions.testParameterInjector}", - 'paparazzi' : "app.cash.paparazzi:paparazzi:${versions.paparazzi}" - ], - 'material' : [ - 'material' : "com.google.android.material:material:${versions.material}", - 'mdcComposeThemeAdapter': "com.google.android.material:compose-theme-adapter:${versions.mdcComposeThemeAdapter}" - ], - 'xprocessing' : "androidx.room:room-compiler-processing:${versions.xprocessing}", - 'xprocessingTesting' : "androidx.room:room-compiler-processing-testing:${versions.xprocessing}", - ] - ext.MAVEN_PUBLISH_VERSION = versions.mavenPublish - repositories { - google() - mavenCentral() - gradlePluginPortal() - } - dependencies { - classpath "com.android.tools.build:gradle:${versions.gradle}" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN" - classpath "com.karumi:shot:${versions.shot}" - classpath "com.vanniktech:gradle-maven-publish-plugin:${versions.mavenPublish}" - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -plugins { - id "io.gitlab.arturbosch.detekt" version "1.23.6" - id "com.google.devtools.ksp" version "$KSP_VERSION" - id "com.vanniktech.maven.publish" version "$MAVEN_PUBLISH_VERSION" - id "org.jetbrains.kotlin.plugin.compose" version "$KOTLIN" -} - -allprojects { - apply from: "$rootDir/detekt/detekt.gradle" - repositories { - google() - mavenCentral() - } - dependencies { - detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:${versions.detekt}" - } -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 000000000..4eb8e98d2 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,52 @@ +buildscript { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + dependencies { + classpath("com.android.tools.build:gradle:${libs.versions.agp.get()}") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${libs.versions.kotlin.get()}") + classpath("com.karumi:shot:${libs.versions.shot.get()}") + classpath("com.vanniktech:gradle-maven-publish-plugin:${libs.versions.mavenPublish.get()}") + } +} + +plugins { + alias(libs.plugins.detekt) + alias(libs.plugins.ksp) + alias(libs.plugins.vanniktech.mavenPublish) + alias(libs.plugins.kotlin.compose) +} + +subprojects { + apply(plugin = "io.gitlab.arturbosch.detekt") + extensions.configure { + config.setFrom(files("$rootDir/detekt/detekt.yml")) + } + dependencies { + "detektPlugins"("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.8") + } + + plugins.withId("com.vanniktech.maven.publish.base") { + extensions.configure("publishing") { + repositories { + maven { + name = "GitHubPackages" + val repoSlug = System.getenv("GITHUB_REPOSITORY") + ?: providers.gradleProperty("githubPackagesRepository").orNull + ?: "mena97villalobos/Showkase" + url = uri("https://maven.pkg.github.com/$repoSlug") + credentials { + username = System.getenv("GITHUB_ACTOR") + ?: providers.gradleProperty("gpr.user").orNull + ?: "" + password = System.getenv("GITHUB_TOKEN") + ?: providers.gradleProperty("gpr.key").orNull + ?: "" + } + } + } + } + } +} diff --git a/detekt/detekt.gradle b/detekt/detekt.gradle deleted file mode 100644 index c5fdbde91..000000000 --- a/detekt/detekt.gradle +++ /dev/null @@ -1,5 +0,0 @@ -apply plugin: "io.gitlab.arturbosch.detekt" - -detekt { - config = files("$rootDir/detekt/detekt.yml") -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000..e58d9bf1d --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,102 @@ +[versions] +agp = "9.2.1" +kotlin = "2.3.21" +ksp = "2.3.7" + +androidXTestCore = "1.7.0" +androidXTestRules = "1.7.0" +appcompat = "1.7.1" +assertJ = "3.27.7" +compose = "1.11.1" +composeActivity = "1.13.0" +composeConstraintLayout = "1.1.1" +composeMaterial3 = "1.4.0" +composeMaterialIcons = "1.7.8" +composeNavigation = "2.9.8" +corektx = "1.18.0" +detekt = "1.23.8" +googleTruth = "1.4.5" +junit = "4.13.2" +junitImplementation = "1.3.0" +kotlinCompileTesting = "0.12.1" +kotlinPoet = "2.3.0" +lifecycle = "2.10.0" +material = "1.13.0" +mavenPublish = "0.36.0" +picasso = "2.71828" +roborazzi = "1.60.0" +robolectric = "4.14.1" +shot = "6.1.0" +strikt = "0.35.1" +testParameterInjector = "1.22" +testRunner = "1.7.0" + +[libraries] +# Compose +compose-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "composeActivity" } +compose-composeNavigation = { module = "androidx.navigation:navigation-compose", version.ref = "composeNavigation" } +compose-composeRuntime = { module = "androidx.compose.runtime:runtime", version.ref = "compose" } +compose-constraintLayout = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "composeConstraintLayout" } +compose-core = { module = "androidx.compose.ui:ui", version.ref = "compose" } +compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" } +compose-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } +compose-toolingPreview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } +compose-layout = { module = "androidx.compose.foundation:foundation-layout", version.ref = "compose" } +compose-material = { module = "androidx.compose.material:material", version.ref = "compose" } +compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "composeMaterial3" } +compose-materialIconsCore = { module = "androidx.compose.material:material-icons-core", version.ref = "composeMaterialIcons" } +compose-savedInstanceState = { module = "androidx.compose.runtime:runtime-saveable", version.ref = "compose" } +compose-uiTest = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" } +compose-uiLiveData = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "compose" } + +# Kotlin tooling +kotlinCompileTesting = { module = "dev.zacsweers.kctfork:core", version.ref = "kotlinCompileTesting" } +kotlinCompileTestingKsp = { module = "dev.zacsweers.kctfork:ksp", version.ref = "kotlinCompileTesting" } +kotlinPoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinPoet" } +kotlinJavaPoetInterop = { module = "com.squareup:kotlinpoet-javapoet", version.ref = "kotlinPoet" } +kotlinPoetKsp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinPoet" } +kotlinXMetadata = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin" } +ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } + +# Image loading +imageLoading-picasso = { module = "com.squareup.picasso:picasso", version.ref = "picasso" } + +# Support +support-appCompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } +support-ktx = { module = "androidx.core:core-ktx", version.ref = "corektx" } +support-lifecycleComposeViewModel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" } +support-lifecycleComposeRuntime = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" } + +# Test +test-androidXTestCore = { module = "androidx.test:core", version.ref = "androidXTestCore" } +test-androidXTestRules = { module = "androidx.test:rules", version.ref = "androidXTestRules" } +test-androidxTestRunner = { module = "androidx.test:runner", version.ref = "testRunner" } +test-assertJ = { module = "org.assertj:assertj-core", version.ref = "assertJ" } +test-googleTruth = { module = "com.google.truth:truth", version.ref = "googleTruth" } +test-junit = { module = "junit:junit", version.ref = "junit" } +test-junitImplementation = { module = "androidx.test.ext:junit", version.ref = "junitImplementation" } +test-roborazzi = { module = "io.github.takahirom.roborazzi:roborazzi", version.ref = "roborazzi" } +test-roborazziCompose = { module = "io.github.takahirom.roborazzi:roborazzi-compose", version.ref = "roborazzi" } +test-roborazziJunitRule = { module = "io.github.takahirom.roborazzi:roborazzi-junit-rule", version.ref = "roborazzi" } +test-robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } +test-shotAndroid = { module = "com.karumi:shot-android", version.ref = "shot" } +test-strikt = { module = "io.strikt:strikt-core", version.ref = "strikt" } +test-testParameterInjector = { module = "com.google.testparameterinjector:test-parameter-injector", version.ref = "testParameterInjector" } + +# Material +material-material = { module = "com.google.android.material:material", version.ref = "material" } + +# Detekt formatting +detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } +vanniktech-mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" } +roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" } +shot = { id = "shot", version.ref = "shot" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ff23a68d7..588ef485d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip -networkTimeout=10000 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +networkTimeout=60000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sample-submodule-2/build.gradle b/sample-submodule-2/build.gradle deleted file mode 100644 index 4d61725c0..000000000 --- a/sample-submodule-2/build.gradle +++ /dev/null @@ -1,83 +0,0 @@ -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'org.jetbrains.kotlin.plugin.compose' -} - -if (project.hasProperty('useKsp')) { - apply plugin: 'com.google.devtools.ksp' -} else { - apply plugin: 'kotlin-kapt' - kapt { - correctErrorTypes = true - arguments { - arg("multiPreviewType", "com.airbnb.android.submodule.showkasesample.FontPreview") - } - } -} - -android { - namespace "com.airbnb.android.submodule.showkasesample" - - defaultConfig { - minSdkVersion 21 - compileSdk 36 - targetSdkVersion 33 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - buildFeatures { - compose true - } - // Added to avoid this error - - // Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction - // > 2 files found with path 'META-INF/AL2.0' from inputs: - packagingOptions { - exclude 'META-INF/AL2.0' - exclude 'META-INF/LGPL2.1' - } -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - // Support Libraries - implementation deps.support.appCompat - implementation deps.support.ktx - implementation deps.support.lifecycleComposeViewModel - implementation deps.support.lifecycleComposeRuntime - - // Showkase - implementation project(':showkase') - implementation project(':sample-submodule') - - if (project.hasProperty('useKsp')) { - ksp project(':showkase-processor') - } else { - kapt project(':showkase-processor') - } - - // Compose - implementation deps.compose.activityCompose - implementation deps.compose.composeRuntime - implementation deps.compose.constraintLayout - implementation deps.compose.core - implementation deps.compose.foundation - implementation deps.compose.tooling - implementation deps.compose.layout - implementation deps.compose.material - implementation deps.compose.savedInstanceState - implementation deps.compose.uiLiveData - androidTestImplementation deps.compose.uiTest -} diff --git a/sample-submodule-2/build.gradle.kts b/sample-submodule-2/build.gradle.kts new file mode 100644 index 000000000..4eda76ee3 --- /dev/null +++ b/sample-submodule-2/build.gradle.kts @@ -0,0 +1,63 @@ +plugins { + id("com.android.library") + alias(libs.plugins.kotlin.compose) + id("com.google.devtools.ksp") +} + +android { + namespace = "com.airbnb.android.submodule2.showkasesample" + + defaultConfig { + minSdk = 23 + compileSdk = 36 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + buildFeatures { + compose = true + } + + packaging { + resources { + excludes += listOf("META-INF/AL2.0", "META-INF/LGPL2.1") + } + } + lint { + baseline = file("lint-baseline.xml") + } +} + +kotlin { + jvmToolchain(21) +} + +dependencies { + implementation(libs.support.appCompat) + implementation(libs.support.ktx) + implementation(libs.support.lifecycleComposeViewModel) + implementation(libs.support.lifecycleComposeRuntime) + + implementation(project(":showkase")) + implementation(project(":sample-submodule")) + + ksp(project(":showkase-processor")) + + implementation(libs.compose.activityCompose) + implementation(libs.compose.composeRuntime) + implementation(libs.compose.constraintLayout) + implementation(libs.compose.core) + implementation(libs.compose.foundation) + implementation(libs.compose.tooling) + implementation(libs.compose.layout) + implementation(libs.compose.material) + implementation(libs.compose.savedInstanceState) + implementation(libs.compose.uiLiveData) + androidTestImplementation(libs.compose.uiTest) +} diff --git a/sample-submodule-2/lint-baseline.xml b/sample-submodule-2/lint-baseline.xml new file mode 100644 index 000000000..62e76974a --- /dev/null +++ b/sample-submodule-2/lint-baseline.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/sample-submodule-2/src/main/java/com/airbnb/android/submodule/showkasesample/SamplePreview.kt b/sample-submodule-2/src/main/java/com/airbnb/android/submodule2/showkasesample/SamplePreview.kt similarity index 60% rename from sample-submodule-2/src/main/java/com/airbnb/android/submodule/showkasesample/SamplePreview.kt rename to sample-submodule-2/src/main/java/com/airbnb/android/submodule2/showkasesample/SamplePreview.kt index 4f948f91e..ee71f9dfd 100644 --- a/sample-submodule-2/src/main/java/com/airbnb/android/submodule/showkasesample/SamplePreview.kt +++ b/sample-submodule-2/src/main/java/com/airbnb/android/submodule2/showkasesample/SamplePreview.kt @@ -1,7 +1,8 @@ -package com.airbnb.android.submodule.showkasesample +package com.airbnb.android.submodule2.showkasesample import androidx.compose.material.Text import androidx.compose.runtime.Composable +import com.airbnb.android.submodule.showkasesample.FontPreview @FontPreview @Composable diff --git a/sample-submodule/build.gradle b/sample-submodule/build.gradle deleted file mode 100644 index 5c1c20fba..000000000 --- a/sample-submodule/build.gradle +++ /dev/null @@ -1,82 +0,0 @@ -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'org.jetbrains.kotlin.plugin.compose' -} - -if (project.hasProperty('useKsp')) { - apply plugin: 'com.google.devtools.ksp' -} else { - apply plugin: 'kotlin-kapt' - kapt { - correctErrorTypes = true - arguments { - arg("multiPreviewType","com.airbnb.android.submodule.showkasesample.LocalePreview") - arg("multiPreviewType", "com.airbnb.android.submodule.showkasesample.FontPreview") - } - } -} - -android { - namespace "com.airbnb.android.submodule.showkasesample" - - defaultConfig { - minSdkVersion 21 - compileSdk 36 - targetSdkVersion 33 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - buildFeatures { - compose true - } - // Added to avoid this error - - // Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction - // > 2 files found with path 'META-INF/AL2.0' from inputs: - packagingOptions { - exclude 'META-INF/AL2.0' - exclude 'META-INF/LGPL2.1' - } -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - // Support Libraries - implementation deps.support.appCompat - implementation deps.support.ktx - implementation deps.support.lifecycleComposeViewModel - implementation deps.support.lifecycleComposeRuntime - - // Showkase - implementation project(':showkase') - if (project.hasProperty('useKsp')) { - ksp project(':showkase-processor') - } else { - kapt project(':showkase-processor') - } - - // Compose - implementation deps.compose.activityCompose - implementation deps.compose.composeRuntime - implementation deps.compose.constraintLayout - implementation deps.compose.core - implementation deps.compose.foundation - implementation deps.compose.tooling - implementation deps.compose.layout - implementation deps.compose.material - implementation deps.compose.savedInstanceState - implementation deps.compose.uiLiveData - androidTestImplementation deps.compose.uiTest -} diff --git a/sample-submodule/build.gradle.kts b/sample-submodule/build.gradle.kts new file mode 100644 index 000000000..c64debbd0 --- /dev/null +++ b/sample-submodule/build.gradle.kts @@ -0,0 +1,61 @@ +plugins { + id("com.android.library") + alias(libs.plugins.kotlin.compose) + id("com.google.devtools.ksp") +} + +android { + namespace = "com.airbnb.android.submodule.showkasesample" + + defaultConfig { + minSdk = 23 + compileSdk = 36 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + buildFeatures { + compose = true + } + + packaging { + resources { + excludes += listOf("META-INF/AL2.0", "META-INF/LGPL2.1") + } + } + lint { + baseline = file("lint-baseline.xml") + } +} + +kotlin { + jvmToolchain(21) +} + +dependencies { + implementation(libs.support.appCompat) + implementation(libs.support.ktx) + implementation(libs.support.lifecycleComposeViewModel) + implementation(libs.support.lifecycleComposeRuntime) + + implementation(project(":showkase")) + ksp(project(":showkase-processor")) + + implementation(libs.compose.activityCompose) + implementation(libs.compose.composeRuntime) + implementation(libs.compose.constraintLayout) + implementation(libs.compose.core) + implementation(libs.compose.foundation) + implementation(libs.compose.tooling) + implementation(libs.compose.layout) + implementation(libs.compose.material) + implementation(libs.compose.savedInstanceState) + implementation(libs.compose.uiLiveData) + androidTestImplementation(libs.compose.uiTest) +} diff --git a/sample-submodule/lint-baseline.xml b/sample-submodule/lint-baseline.xml new file mode 100644 index 000000000..8571e912e --- /dev/null +++ b/sample-submodule/lint-baseline.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/build.gradle b/sample/build.gradle deleted file mode 100644 index 823d61749..000000000 --- a/sample/build.gradle +++ /dev/null @@ -1,112 +0,0 @@ -plugins { - id 'com.android.application' - id 'kotlin-android' - id 'shot' - id 'org.jetbrains.kotlin.plugin.compose' -} - -if (project.hasProperty('useKsp')) { - apply plugin: 'com.google.devtools.ksp' - kotlin { - sourceSets.configureEach { - kotlin.srcDirs += "build/generated/ksp/${it.name}/kotlin" - } - } -} else { - apply plugin: 'kotlin-kapt' - kapt { - correctErrorTypes = true - } -} - -android { - namespace "com.airbnb.android.showkasesample" - - defaultConfig { - applicationId "com.airbnb.android.showkasesample" - minSdkVersion 21 - compileSdk 36 - targetSdkVersion 35 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "com.karumi.shot.ShotTestRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - buildFeatures { - compose true - } - // Added to avoid this error - - // Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction - // > 2 files found with path 'META-INF/AL2.0' from inputs: - packagingOptions { - exclude 'META-INF/AL2.0' - exclude 'META-INF/LGPL2.1' - exclude 'META-INF/gradle/incremental.annotation.processors' - } -} -if (project.hasProperty("useKsp")) { - ksp { - arg("skipPrivatePreviews", "true") - } -} else { - kapt { - arguments { - arg("skipPrivatePreviews", "true") - } - } -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - // Showkase dependencies - // TODO(vinaygaba): Using debugImplementation was causing kapt related NonExistentClass errors. - // Figure out a way to enable using debugImplementation - implementation project(':showkase') - implementation project(':sample-submodule') - implementation project(':sample-submodule-2') - if (project.hasProperty('useKsp')) { - ksp project(':showkase-processor') - kspAndroidTest project(':showkase-processor') - } else { - kapt project(':showkase-processor') - kaptAndroidTest project(':showkase-processor') - } - - // Support Libraries - implementation deps.support.appCompat - implementation deps.support.ktx - implementation deps.support.lifecycleComposeViewModel - implementation deps.support.lifecycleComposeRuntime - - // Compose - implementation deps.compose.activityCompose - implementation deps.compose.composeRuntime - implementation deps.compose.constraintLayout - implementation deps.compose.core - implementation deps.compose.foundation - implementation deps.compose.tooling - implementation deps.compose.layout - implementation deps.compose.material - implementation deps.compose.savedInstanceState - implementation deps.compose.uiLiveData - androidTestImplementation deps.compose.uiTest - - // Image loading - implementation deps.imageLoading.picasso - - // Testing - testImplementation deps.test.junit - androidTestImplementation deps.test.junitImplementation - androidTestImplementation project(':showkase-screenshot-testing-shot') -} diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts new file mode 100644 index 000000000..11cb58b71 --- /dev/null +++ b/sample/build.gradle.kts @@ -0,0 +1,87 @@ +plugins { + id("com.android.application") + id("shot") + alias(libs.plugins.kotlin.compose) + id("com.google.devtools.ksp") +} + +android { + namespace = "com.airbnb.android.showkasesample" + + defaultConfig { + applicationId = "com.airbnb.android.showkasesample" + minSdk = 23 + compileSdk = 36 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + testInstrumentationRunner = "com.karumi.shot.ShotTestRunner" + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + buildFeatures { + compose = true + } + + packaging { + resources { + excludes += listOf( + "META-INF/AL2.0", + "META-INF/LGPL2.1", + "META-INF/gradle/incremental.annotation.processors", + ) + } + } + lint { + baseline = file("lint-baseline.xml") + } +} + +ksp { + arg("skipPrivatePreviews", "true") +} + +kotlin { + jvmToolchain(21) + sourceSets.configureEach { + kotlin.srcDir("build/generated/ksp/$name/kotlin") + } +} + +dependencies { + implementation(project(":showkase")) + implementation(project(":sample-submodule")) + implementation(project(":sample-submodule-2")) + ksp(project(":showkase-processor")) + kspAndroidTest(project(":showkase-processor")) + + implementation(libs.support.appCompat) + implementation(libs.support.ktx) + implementation(libs.support.lifecycleComposeViewModel) + implementation(libs.support.lifecycleComposeRuntime) + + implementation(libs.compose.activityCompose) + implementation(libs.compose.composeRuntime) + implementation(libs.compose.constraintLayout) + implementation(libs.compose.core) + implementation(libs.compose.foundation) + implementation(libs.compose.tooling) + implementation(libs.compose.layout) + implementation(libs.compose.material) + implementation(libs.compose.materialIconsCore) + implementation(libs.compose.savedInstanceState) + implementation(libs.compose.uiLiveData) + androidTestImplementation(libs.compose.uiTest) + + implementation(libs.imageLoading.picasso) + + testImplementation(libs.test.junit) + androidTestImplementation(libs.test.junitImplementation) + androidTestImplementation(project(":showkase-screenshot-testing-shot")) +} diff --git a/sample/lint-baseline.xml b/sample/lint-baseline.xml new file mode 100644 index 000000000..803e83890 --- /dev/null +++ b/sample/lint-baseline.xml @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index e59a194c2..000000000 --- a/settings.gradle +++ /dev/null @@ -1,15 +0,0 @@ -rootProject.name = "Showkase" -include ':showkase' -include ':showkase-processor' -include ':showkase-processor-testing' -include ':showkase-annotation' -include ':sample' -include ':sample-submodule' -include ':sample-submodule-2' -include ':showkase-screenshot-testing' -include ':showkase-browser-testing' -include ':showkase-browser-testing-submodule' -include ':showkase-browser-testing-submodule-2' -include ':showkase-screenshot-testing-shot' -include ':showkase-screenshot-testing-paparazzi-sample' -include ':showkase-screenshot-testing-paparazzi' \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 000000000..6b665fa37 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,34 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "Showkase" +include( + ":showkase", + ":showkase-models", + ":showkase-processor", + ":showkase-processor-testing", + ":showkase-annotation", + ":sample", + ":sample-submodule", + ":sample-submodule-2", + ":showkase-screenshot-testing", + ":showkase-browser-testing", + ":showkase-browser-testing-submodule", + ":showkase-browser-testing-submodule-2", + ":showkase-screenshot-testing-shot", + ":showkase-screenshot-testing-roborazzi", + ":showkase-screenshot-testing-roborazzi-sample", +) diff --git a/showkase-annotation/build.gradle b/showkase-annotation/build.gradle deleted file mode 100644 index d377e3689..000000000 --- a/showkase-annotation/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -import com.vanniktech.maven.publish.JavaLibrary -import com.vanniktech.maven.publish.JavadocJar - -plugins { - id 'java-library' - id 'kotlin' - id 'com.vanniktech.maven.publish' -} - -kotlin { - jvmToolchain(17) -} - -mavenPublishing { - configure(new JavaLibrary(new JavadocJar.Javadoc(), true)) -} diff --git a/showkase-annotation/build.gradle.kts b/showkase-annotation/build.gradle.kts new file mode 100644 index 000000000..9c531a46d --- /dev/null +++ b/showkase-annotation/build.gradle.kts @@ -0,0 +1,17 @@ +import com.vanniktech.maven.publish.JavaLibrary +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.SourcesJar + +plugins { + `java-library` + id("org.jetbrains.kotlin.jvm") + id("com.vanniktech.maven.publish") +} + +kotlin { + jvmToolchain(21) +} + +mavenPublishing { + configure(JavaLibrary(JavadocJar.Javadoc(), SourcesJar.Sources())) +} diff --git a/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseCodegenMetadata.kt b/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseCodegenMetadata.kt index 6ca1f0fb0..e6bb64800 100644 --- a/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseCodegenMetadata.kt +++ b/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseCodegenMetadata.kt @@ -41,5 +41,8 @@ annotation class ShowkaseCodegenMetadata( val isDefaultStyle: Boolean = false, val generatedPropertyName: String = "", val tags: Array = [], - val extraMetadata: Array = [] + val extraMetadata: Array = [], + val isDialog: Boolean = false, + val dialogButtonText: String = "", + val dialogHideButtonText: String = "", ) diff --git a/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseComposable.kt b/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseComposable.kt index 59a6d1de0..b906108da 100644 --- a/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseComposable.kt +++ b/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseComposable.kt @@ -75,13 +75,7 @@ annotation class ShowkaseComposable( val defaultStyle: Boolean = false, val tags: Array = [], val extraMetadata: Array = [], - val screenshotCaptureConfig: ScreenshotCaptureConfig = ScreenshotCaptureConfig( - // Need to specify default values here or else KAPT throws an error - type = ScreenshotCaptureType.SingleStaticImage, - durationMillis = 1000, - framerate = 10, - offsetsMillis = [0, 200, 400, 600, 800, 1000], - ), + val screenshotCaptureConfig: ScreenshotCaptureConfig = ScreenshotCaptureConfig(), ) /** @@ -91,25 +85,25 @@ annotation class ShowkaseComposable( @Retention(AnnotationRetention.SOURCE) annotation class ScreenshotCaptureConfig( /** - * Used by Paparazzi snapshot testing to determine if the component has any animation, and how to capture + * Used by screenshot testing to determine if the component has any animation, and how to capture * the screenshot. */ val type: ScreenshotCaptureType = ScreenshotCaptureType.SingleStaticImage, /** - * Used by Paparazzi screenshot testing when [type] is set to + * Used by screenshot testing when [type] is set to * [ScreenshotCaptureType.SingleAnimatedImage]. Determines the duration the animation * will be played in milliseconds. */ val durationMillis: Int = 1000, /** - * Used by Paparazzi screenshot testing when [type] is set to + * Used by screenshot testing when [type] is set to * [ScreenshotCaptureType.SingleAnimatedImage]. Determines how many frames * will be captured per second. 10 fps is chosen as a default to balance fidelity, file size, test * execution time, and flakiness. */ val framerate: Int = 10, /** - * Used by Paparazzi screenshot testing when [type] is set to + * Used by screenshot testing when [type] is set to * [ScreenshotCaptureType.MultipleImagesAtOffsets]. One separate screenshot will be taken * at each of the time offsets provided here. */ @@ -135,8 +129,6 @@ enum class ScreenshotCaptureType { /** * Multiple static screenshots will be taken of the Composable, with the animation advanced to the * time offsets provided in [ShowkaseComposable.captureOffsetsMillis]. - * - * NOTE: This isn't working currently in Paparazzi, see https://github.com/cashapp/paparazzi/pull/1645. */ MultipleImagesAtOffsets } diff --git a/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseDialog.kt b/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseDialog.kt new file mode 100644 index 000000000..ea60c71c0 --- /dev/null +++ b/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseDialog.kt @@ -0,0 +1,92 @@ +package com.airbnb.android.showkase.annotation + +/** + * Used to annotate @Composable functions that represent a `Dialog`, full-screen sheet, or any + * other composable that would be disruptive to render inline inside the Showkase browser. Instead + * of composing the function immediately when the entry is visible, the Showkase browser renders a + * gated toggle button. The annotated composable is only invoked once the user taps the button, and + * is removed from composition when the button is tapped again. + * + * Use this for any composable that takes over the screen, such as + * `androidx.compose.ui.window.Dialog` previews: + * + * @ShowkaseDialog(name = "Confirm dialog", group = "Dialogs") + * @Composable + * fun MyDialogPreview() { + * Dialog(onDismissRequest = {}) { + * ....... + * } + * } + * + *

+ * Note: Make sure that you add this annotation to only those functions that don't accept any + * parameters. If your function accepts a parameters, wrap it inside another function that doesn't + * accept any parameters. + * + * For example, here is a @Composable function that requires parameters - + * + * @Composable + * fun MyDialog(title: String) { + * ....... + * ....... + * } + * + * In order to make this function compatible with Showkase, you could further wrap this function + * inside a method that doesn't accept a parameters in the following way: + * + * @ShowkaseDialog(name = "Name", group = "Group") + * @Composable + * fun MyDialogPreview() { + * MyDialog("Title") + * } + * + * This requirement is even needed by the @Preview functions of Jetpack Compose. + * + * @param name The name that should be used to describe your `@Composable` function. If you don't + * pass any value, the name of the composable function is used as the name. + * @param group The grouping key that will be used to group it with other `@Composable` functions + * . This is useful for better organization and discoverability of your components. If you don't + * pass any value for the group, the name of the class that wraps this function is used as the + * group name. If the function is a top level function, the composable is added to a "Default Group". + * @param widthDp The width that your component will be rendered in inside the Showkase browser. + * Use this to restrict the size of your preview inside the Showkase browser. + * @param heightDp The height that your component will be rendered in inside the Showkase browser. + * Use this to restrict the size of your preview inside the Showkase browser. + * @param skip Use this boolean when you want to skip this composable from the Showkase browser. A + * use case of this might be when you want a composable that's annotated with @Preview to show previews + * in Android Studio but don't want to necessary show it in the component browser that's autogenerated + * by Showkase + * @param defaultStyle Used to represent a composable function is the default style variant of a given + * composable. More information on how Showkase allows you to represent component styles in this section - + * https://github.com/airbnb/Showkase#representing-component-styles-in-showkase + * @param tags Various string values that will be propagated to the Showkase browser to allow additional + * filtering or categorization. + * @param extraMetadata Various string values that are **not** used by the standard Showkase browser + * but are still available in the generated `ShowkaseBrowserComponent` object. This may be useful when + * extra data is needed for attributing components during other processes (e.g. static analysis, + * displaying attributions in a custom component browser). + * @param screenshotCaptureConfig Configures how screenshot tests should capture the Composable content. + * @param buttonText Label rendered on the toggle button while the dialog is hidden. If empty, the + * default localized "Show Dialog" string is used. + * @param hideButtonText Label rendered on the toggle button while the dialog is visible. If empty, + * the default localized "Hide Dialog" string is used. + */ +@MustBeDocumented +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.FUNCTION) +@Repeatable +@Suppress("LongParameterList") +annotation class ShowkaseDialog( + val name: String = "", + val group: String = "", + val styleName: String = "", + val widthDp: Int = -1, + val heightDp: Int = -1, + val skip: Boolean = false, + val defaultStyle: Boolean = false, + val tags: Array = [], + val extraMetadata: Array = [], + val screenshotCaptureConfig: ScreenshotCaptureConfig = ScreenshotCaptureConfig(), + val buttonText: String = "", + val hideButtonText: String = "", +) diff --git a/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseMultiPreviewCodegenMetadata.kt b/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseMultiPreviewCodegenMetadata.kt index 938a7587a..806b610de 100644 --- a/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseMultiPreviewCodegenMetadata.kt +++ b/showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseMultiPreviewCodegenMetadata.kt @@ -1,4 +1,7 @@ package com.airbnb.android.showkase.annotation + +@MustBeDocumented +@Retention(AnnotationRetention.BINARY) @Target(AnnotationTarget.FUNCTION) annotation class ShowkaseMultiPreviewCodegenMetadata( val previewName: String, diff --git a/showkase-browser-testing-submodule-2/build.gradle b/showkase-browser-testing-submodule-2/build.gradle deleted file mode 100644 index 420d7e28d..000000000 --- a/showkase-browser-testing-submodule-2/build.gradle +++ /dev/null @@ -1,97 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'org.jetbrains.kotlin.plugin.compose' - -if (project.hasProperty('useKsp')) { - apply plugin: 'com.google.devtools.ksp' - ksp { - arg("skipPrivatePreviews", "true") - } -} else { - apply plugin: 'kotlin-kapt' - kapt { - correctErrorTypes = true - arguments { - arg("multiPreviewType","com.vinaygaba.showkase_browser_testing_submodule.two.LocalePreview") - arg("multiPreviewType", "com.vinaygaba.showkase_browser_testing_submodule.two.EnglishLocalePreview") - arg("skipPrivatePreviews", "true") - } - } -} - -android { - namespace "com.airbnb.android.showkase_browser_testing_submodule.two" - // Added to avoid this error - - // Execution failed for task ':showkase-processor-testing:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade - // > More than one file was found with OS independent path 'META-INF/gradle/incremental.annotation.processors' - packagingOptions { - exclude 'META-INF/gradle/incremental.annotation.processors' - exclude("META-INF/*.kotlin_module") - } - defaultConfig { - minSdkVersion 26 - compileSdk 36 - targetSdkVersion 33 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - // The following argument makes the Android Test Orchestrator run its - // "pm clear" command after each test invocation. This command ensures - // that the app's state is completely cleared between tests. - testInstrumentationRunnerArguments clearPackageData: 'true' - } - buildFeatures { - compose true - } - // Added to avoid this error - - // Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction - // > 2 files found with path 'META-INF/AL2.0' from inputs: - packagingOptions { - exclude 'META-INF/AL2.0' - exclude 'META-INF/LGPL2.1' - } - configurations { - all { - // work around this error: - // Duplicate class org.intellij.lang.annotations.Identifier found in modules annotations-12.0 (com.intellij:annotations:12.0) and annotations-13.0 (org.jetbrains:annotations:13.0) - exclude group: "com.intellij", module: "annotations" - } - } -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - // Support Libraries - implementation deps.support.appCompat - - // Showkase - implementation project(':showkase') - if (project.hasProperty('useKsp')) { - ksp project(':showkase-processor') - } else { - kapt project(':showkase-processor') - } - implementation project(':showkase-processor') - implementation project(':showkase-screenshot-testing') - - // Compose - implementation deps.compose.activityCompose - implementation deps.compose.composeRuntime - implementation deps.compose.core - implementation deps.compose.foundation - implementation deps.compose.tooling - androidTestImplementation deps.compose.uiTest - - // Material - implementation deps.material.material - implementation deps.material.mdcComposeThemeAdapter - - // Testing - androidTestImplementation deps.test.junitImplementation - androidTestImplementation deps.test.androidXTestCore - androidTestImplementation deps.test.androidXTestRules - androidTestImplementation deps.test.androidxTestRunner -} diff --git a/showkase-browser-testing-submodule-2/build.gradle.kts b/showkase-browser-testing-submodule-2/build.gradle.kts new file mode 100644 index 000000000..04d42db40 --- /dev/null +++ b/showkase-browser-testing-submodule-2/build.gradle.kts @@ -0,0 +1,69 @@ +plugins { + id("com.android.library") + alias(libs.plugins.kotlin.compose) + id("com.google.devtools.ksp") +} + +ksp { + arg("skipPrivatePreviews", "true") +} + +android { + namespace = "com.airbnb.android.showkase_browser_testing_submodule.two" + + defaultConfig { + minSdk = 26 + compileSdk = 36 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["clearPackageData"] = "true" + } + + buildFeatures { + compose = true + } + + packaging { + resources { + excludes += listOf( + "META-INF/gradle/incremental.annotation.processors", + "META-INF/*.kotlin_module", + "META-INF/AL2.0", + "META-INF/LGPL2.1", + ) + } + } + lint { + baseline = file("lint-baseline.xml") + } +} + +configurations.all { + exclude(group = "com.intellij", module = "annotations") +} + +kotlin { + jvmToolchain(21) +} + +dependencies { + implementation(libs.support.appCompat) + + implementation(project(":showkase")) + ksp(project(":showkase-processor")) + implementation(project(":showkase-processor")) + implementation(project(":showkase-screenshot-testing")) + + implementation(libs.compose.activityCompose) + implementation(libs.compose.composeRuntime) + implementation(libs.compose.core) + implementation(libs.compose.foundation) + implementation(libs.compose.tooling) + androidTestImplementation(libs.compose.uiTest) + + implementation(libs.material.material) + + androidTestImplementation(libs.test.junitImplementation) + androidTestImplementation(libs.test.androidXTestCore) + androidTestImplementation(libs.test.androidXTestRules) + androidTestImplementation(libs.test.androidxTestRunner) +} diff --git a/showkase-browser-testing-submodule-2/lint-baseline.xml b/showkase-browser-testing-submodule-2/lint-baseline.xml new file mode 100644 index 000000000..aced025c1 --- /dev/null +++ b/showkase-browser-testing-submodule-2/lint-baseline.xml @@ -0,0 +1,5 @@ + + + + diff --git a/showkase-browser-testing-submodule/build.gradle b/showkase-browser-testing-submodule/build.gradle deleted file mode 100644 index 6ec0ac72a..000000000 --- a/showkase-browser-testing-submodule/build.gradle +++ /dev/null @@ -1,89 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'org.jetbrains.kotlin.plugin.compose' - -if (project.hasProperty('useKsp')) { - apply plugin: 'com.google.devtools.ksp' -} else { - apply plugin: 'kotlin-kapt' - kapt { - correctErrorTypes = true - } -} - -android { - namespace "com.airbnb.android.showkase_browser_testing_submodule" - // Added to avoid this error - - // Execution failed for task ':showkase-processor-testing:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade - // > More than one file was found with OS independent path 'META-INF/gradle/incremental.annotation.processors' - packagingOptions { - exclude 'META-INF/gradle/incremental.annotation.processors' - exclude("META-INF/*.kotlin_module") - } - defaultConfig { - minSdkVersion 26 - compileSdk 36 - targetSdkVersion 33 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - // The following argument makes the Android Test Orchestrator run its - // "pm clear" command after each test invocation. This command ensures - // that the app's state is completely cleared between tests. - testInstrumentationRunnerArguments clearPackageData: 'true' - } - buildFeatures { - compose true - } - // Added to avoid this error - - // Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction - // > 2 files found with path 'META-INF/AL2.0' from inputs: - packagingOptions { - exclude 'META-INF/AL2.0' - exclude 'META-INF/LGPL2.1' - } - configurations { - all { - // work around this error: - // Duplicate class org.intellij.lang.annotations.Identifier found in modules annotations-12.0 (com.intellij:annotations:12.0) and annotations-13.0 (org.jetbrains:annotations:13.0) - exclude group: "com.intellij", module: "annotations" - } - } -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - // Support Libraries - implementation deps.support.appCompat - - // Showkase - implementation project(':showkase') - if (project.hasProperty('useKsp')) { - ksp project(':showkase-processor') - } else { - kapt project(':showkase-processor') - } - implementation project(':showkase-processor') - implementation project(':showkase-screenshot-testing') - - // Compose - implementation deps.compose.activityCompose - implementation deps.compose.composeRuntime - implementation deps.compose.core - implementation deps.compose.foundation - implementation deps.compose.tooling - androidTestImplementation deps.compose.uiTest - - // Material - implementation deps.material.material - implementation deps.material.mdcComposeThemeAdapter - - // Testing - androidTestImplementation deps.test.junitImplementation - androidTestImplementation deps.test.androidXTestCore - androidTestImplementation deps.test.androidXTestRules - androidTestImplementation deps.test.androidxTestRunner -} diff --git a/showkase-browser-testing-submodule/build.gradle.kts b/showkase-browser-testing-submodule/build.gradle.kts new file mode 100644 index 000000000..43348e1eb --- /dev/null +++ b/showkase-browser-testing-submodule/build.gradle.kts @@ -0,0 +1,65 @@ +plugins { + id("com.android.library") + alias(libs.plugins.kotlin.compose) + id("com.google.devtools.ksp") +} + +android { + namespace = "com.airbnb.android.showkase_browser_testing_submodule" + + defaultConfig { + minSdk = 26 + compileSdk = 36 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["clearPackageData"] = "true" + } + + buildFeatures { + compose = true + } + + packaging { + resources { + excludes += listOf( + "META-INF/gradle/incremental.annotation.processors", + "META-INF/*.kotlin_module", + "META-INF/AL2.0", + "META-INF/LGPL2.1", + ) + } + } + lint { + baseline = file("lint-baseline.xml") + } +} + +configurations.all { + exclude(group = "com.intellij", module = "annotations") +} + +kotlin { + jvmToolchain(21) +} + +dependencies { + implementation(libs.support.appCompat) + + implementation(project(":showkase")) + ksp(project(":showkase-processor")) + implementation(project(":showkase-processor")) + implementation(project(":showkase-screenshot-testing")) + + implementation(libs.compose.activityCompose) + implementation(libs.compose.composeRuntime) + implementation(libs.compose.core) + implementation(libs.compose.foundation) + implementation(libs.compose.tooling) + androidTestImplementation(libs.compose.uiTest) + + implementation(libs.material.material) + + androidTestImplementation(libs.test.junitImplementation) + androidTestImplementation(libs.test.androidXTestCore) + androidTestImplementation(libs.test.androidXTestRules) + androidTestImplementation(libs.test.androidxTestRunner) +} diff --git a/showkase-browser-testing-submodule/lint-baseline.xml b/showkase-browser-testing-submodule/lint-baseline.xml new file mode 100644 index 000000000..aced025c1 --- /dev/null +++ b/showkase-browser-testing-submodule/lint-baseline.xml @@ -0,0 +1,5 @@ + + + + diff --git a/showkase-browser-testing/build.gradle b/showkase-browser-testing/build.gradle deleted file mode 100644 index ab6d2d5b6..000000000 --- a/showkase-browser-testing/build.gradle +++ /dev/null @@ -1,106 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'org.jetbrains.kotlin.plugin.compose' - -if (project.hasProperty('useKsp')) { - apply plugin: 'com.google.devtools.ksp' - ksp { - arg("skipPrivatePreviews", "true") - } -} else { - apply plugin: 'kotlin-kapt' - kapt { - correctErrorTypes = true - arguments { - arg("skipPrivatePreviews", "true") - } - } -} - -android { - namespace "com.airbnb.android.showkase_browser_testing" - // Added to avoid this error - - // Execution failed for task ':showkase-processor-testing:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade - // > More than one file was found with OS independent path 'META-INF/gradle/incremental.annotation.processors' - packagingOptions { - exclude 'META-INF/gradle/incremental.annotation.processors' - exclude("META-INF/*.kotlin_module") - } - - defaultConfig { - minSdkVersion 26 - compileSdk 36 - targetSdkVersion 33 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - // The following argument makes the Android Test Orchestrator run its - // "pm clear" command after each test invocation. This command ensures - // that the app's state is completely cleared between tests. - testInstrumentationRunnerArguments clearPackageData: 'true' - - if (project.hasProperty('useKsp')) { - buildConfigField "boolean", "IS_RUNNING_KSP", "true" - } else { - buildConfigField "boolean", "IS_RUNNING_KSP", "false" - } - } - buildFeatures { - compose true - buildConfig = true - } - // Added to avoid this error - - // Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction - // > 2 files found with path 'META-INF/AL2.0' from inputs: - packagingOptions { - exclude 'META-INF/AL2.0' - exclude 'META-INF/LGPL2.1' - } - configurations { - all { - // work around this error: - // Duplicate class org.intellij.lang.annotations.Identifier found in modules annotations-12.0 (com.intellij:annotations:12.0) and annotations-13.0 (org.jetbrains:annotations:13.0) - exclude group: "com.intellij", module: "annotations" - } - } -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - // Support Libraries - implementation deps.support.appCompat - // Submodule for testing - implementation project(':showkase-browser-testing-submodule') - implementation project(':showkase-browser-testing-submodule-2') - - // Showkase - implementation project(':showkase') - if (project.hasProperty('useKsp')) { - ksp project(':showkase-processor') - } else { - kapt project(':showkase-processor') - } - implementation project(':showkase-processor') - implementation project(':showkase-screenshot-testing') - - // Compose - implementation deps.compose.activityCompose - implementation deps.compose.composeRuntime - implementation deps.compose.core - implementation deps.compose.foundation - implementation deps.compose.tooling - androidTestImplementation deps.compose.uiTest - - // Material - implementation deps.material.material - implementation deps.material.mdcComposeThemeAdapter - - // Testing - androidTestImplementation deps.test.junitImplementation - androidTestImplementation deps.test.androidXTestCore - androidTestImplementation deps.test.androidXTestRules - androidTestImplementation deps.test.androidxTestRunner -} diff --git a/showkase-browser-testing/build.gradle.kts b/showkase-browser-testing/build.gradle.kts new file mode 100644 index 000000000..1979d5eff --- /dev/null +++ b/showkase-browser-testing/build.gradle.kts @@ -0,0 +1,71 @@ +plugins { + id("com.android.library") + alias(libs.plugins.kotlin.compose) + id("com.google.devtools.ksp") +} + +ksp { + arg("skipPrivatePreviews", "true") +} + +android { + namespace = "com.airbnb.android.showkase_browser_testing" + + defaultConfig { + minSdk = 26 + compileSdk = 36 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["clearPackageData"] = "true" + } + + buildFeatures { + compose = true + } + + packaging { + resources { + excludes += listOf( + "META-INF/gradle/incremental.annotation.processors", + "META-INF/*.kotlin_module", + "META-INF/AL2.0", + "META-INF/LGPL2.1", + ) + } + } + lint { + baseline = file("lint-baseline.xml") + } +} + +configurations.all { + exclude(group = "com.intellij", module = "annotations") +} + +kotlin { + jvmToolchain(21) +} + +dependencies { + implementation(libs.support.appCompat) + implementation(project(":showkase-browser-testing-submodule")) + implementation(project(":showkase-browser-testing-submodule-2")) + + implementation(project(":showkase")) + ksp(project(":showkase-processor")) + implementation(project(":showkase-processor")) + implementation(project(":showkase-screenshot-testing")) + + implementation(libs.compose.activityCompose) + implementation(libs.compose.composeRuntime) + implementation(libs.compose.core) + implementation(libs.compose.foundation) + implementation(libs.compose.tooling) + androidTestImplementation(libs.compose.uiTest) + + implementation(libs.material.material) + + androidTestImplementation(libs.test.junitImplementation) + androidTestImplementation(libs.test.androidXTestCore) + androidTestImplementation(libs.test.androidXTestRules) + androidTestImplementation(libs.test.androidxTestRunner) +} diff --git a/showkase-browser-testing/lint-baseline.xml b/showkase-browser-testing/lint-baseline.xml new file mode 100644 index 000000000..aced025c1 --- /dev/null +++ b/showkase-browser-testing/lint-baseline.xml @@ -0,0 +1,5 @@ + + + + diff --git a/showkase-browser-testing/src/androidTest/java/com/airbnb/android/showkase_browser_testing/ShowkaseBrowserTest.kt b/showkase-browser-testing/src/androidTest/java/com/airbnb/android/showkase_browser_testing/ShowkaseBrowserTest.kt index 094169f58..33b1d8d0a 100644 --- a/showkase-browser-testing/src/androidTest/java/com/airbnb/android/showkase_browser_testing/ShowkaseBrowserTest.kt +++ b/showkase-browser-testing/src/androidTest/java/com/airbnb/android/showkase_browser_testing/ShowkaseBrowserTest.kt @@ -6,7 +6,6 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.platform.app.InstrumentationRegistry import com.airbnb.android.showkase.models.Showkase import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity -import kotlinx.coroutines.delay import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -36,16 +35,7 @@ class ShowcaseBrowserTest { } ) - // This will alter now since KSP supports stacked preview annotations and KAPT does not. - // It is not supported in KAPT because there is no support for repeatable annotations in KAPT - // beyond the source retention KEEP, except for in the new IR backend which was introduced - // in Kotlin 1.6. It will be available in the old backend in Kotlin version 1.7.20. - // See https://youtrack.jetbrains.com/issue/KT-49682 for more information about this. - private val componentSize = if (BuildConfig.IS_RUNNING_KSP) { - 20 - } else { - 10 - } + private val componentSize = 20 @Test fun activity_starts_and_all_the_showkase_ui_elements_are_visible_on_the_screen_and_clickable() { @@ -695,37 +685,34 @@ class ShowcaseBrowserTest { @Test fun stacked_preview_show_up_in_browser() { - // Stacked previews are only supported from ksp, so this is to bypass kapt on CI - if (BuildConfig.IS_RUNNING_KSP) { - composeTestRule.apply { - - verifyLandingScreen( - components = componentSize, - typography = 13, - colors = 4, - ) - // Tap on the "Components" row - clickRowWithText("Components ($componentSize)") + composeTestRule.apply { - waitForIdle() + verifyLandingScreen( + components = componentSize, + typography = 13, + colors = 4, + ) + // Tap on the "Components" row + clickRowWithText("Components ($componentSize)") - onRoot().performTouchInput { - swipeUp() - } + waitForIdle() - waitForIdle() + onRoot().performTouchInput { + swipeUp() + } - clickRowWithText("Group7 (4)") + waitForIdle() - waitForIdle() + clickRowWithText("Group7 (4)") - // Verify that they are all displayed and treated as different components - onNodeWithText("Composable7").assertIsDisplayed() - onNodeWithText("Composable8").assertIsDisplayed() - onNodeWithText("Composable9").assertIsDisplayed() - onNodeWithText("Composable10").assertIsDisplayed() + waitForIdle() + + // Verify that they are all displayed and treated as different components + onNodeWithText("Composable7").assertIsDisplayed() + onNodeWithText("Composable8").assertIsDisplayed() + onNodeWithText("Composable9").assertIsDisplayed() + onNodeWithText("Composable10").assertIsDisplayed() - } } } @@ -783,29 +770,26 @@ class ShowcaseBrowserTest { @Test fun customStackedSubmodulePreviewShowsUpInBrowserForKsp() { - if (BuildConfig.IS_RUNNING_KSP) { - - composeTestRule.apply { + composeTestRule.apply { - verifyLandingScreen( - components = componentSize, - typography = 13, - colors = 4, - ) - // Tap on the "Components" row - clickRowWithText("Components ($componentSize)") + verifyLandingScreen( + components = componentSize, + typography = 13, + colors = 4, + ) + // Tap on the "Components" row + clickRowWithText("Components ($componentSize)") - waitForIdle() + waitForIdle() - clickRowWithText("CustomSubmodulePreview (2)") + clickRowWithText("CustomSubmodulePreview (2)") - waitForIdle() + waitForIdle() - // Verify that they are all displayed and treated as different components - onNodeWithText("CustomShape - CustomSize 200 * 200").assertIsDisplayed() - onNodeWithText("CustomShape - CustomSize 100 * 100").assertIsDisplayed() + // Verify that they are all displayed and treated as different components + onNodeWithText("CustomShape - CustomSize 200 * 200").assertIsDisplayed() + onNodeWithText("CustomShape - CustomSize 100 * 100").assertIsDisplayed() - } } } @@ -830,7 +814,7 @@ class ShowcaseBrowserTest { waitForIdle() - val composables = if (BuildConfig.IS_RUNNING_KSP) 3 else 1 + val composables = 3 clickRowWithText("LocalePreview ($composables)") @@ -865,7 +849,7 @@ class ShowcaseBrowserTest { waitForIdle() - val composables = if (BuildConfig.IS_RUNNING_KSP) 3 else 1 + val composables = 3 clickRowWithText("LocalePreview ($composables)") diff --git a/showkase-browser-testing/src/main/java/com/airbnb/android/showkase_browser_testing/TestComposables.kt b/showkase-browser-testing/src/main/java/com/airbnb/android/showkase_browser_testing/TestComposables.kt index 9ff130895..b2b140774 100644 --- a/showkase-browser-testing/src/main/java/com/airbnb/android/showkase_browser_testing/TestComposables.kt +++ b/showkase-browser-testing/src/main/java/com/airbnb/android/showkase_browser_testing/TestComposables.kt @@ -8,14 +8,11 @@ import androidx.compose.ui.tooling.preview.Preview import com.airbnb.android.showkase.annotation.ShowkaseComposable import com.airbnb.android.showkase.annotation.ShowkaseRoot import com.airbnb.android.showkase.annotation.ShowkaseRootModule -import com.google.android.material.composethemeadapter.MdcTheme @ShowkaseComposable("Composable1", "Group1") @Composable fun TestComposable1() { - MdcTheme { - BasicText(text = "Test Composable1") - } + BasicText(text = "Test Composable1") } @ShowkaseComposable("Composable2", "Group1") diff --git a/showkase-models/build.gradle.kts b/showkase-models/build.gradle.kts new file mode 100644 index 000000000..56fcef6e5 --- /dev/null +++ b/showkase-models/build.gradle.kts @@ -0,0 +1,39 @@ +import com.vanniktech.maven.publish.AndroidMultiVariantLibrary +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.SourcesJar + +plugins { + id("com.android.library") + alias(libs.plugins.kotlin.compose) + id("com.vanniktech.maven.publish") +} + +android { + namespace = "com.airbnb.android.showkase.models" + + defaultConfig { + minSdk = 23 + compileSdk = 36 + } + + buildFeatures { + compose = true + } + lint { + baseline = file("lint-baseline.xml") + } +} + +kotlin { + jvmToolchain(21) +} + +dependencies { + api(project(":showkase-annotation")) + api(libs.compose.composeRuntime) + api(libs.compose.core) +} + +mavenPublishing { + configure(AndroidMultiVariantLibrary(JavadocJar.Empty(), SourcesJar.Sources())) +} diff --git a/showkase-models/lint-baseline.xml b/showkase-models/lint-baseline.xml new file mode 100644 index 000000000..aced025c1 --- /dev/null +++ b/showkase-models/lint-baseline.xml @@ -0,0 +1,5 @@ + + + + diff --git a/showkase/src/main/java/com/airbnb/android/showkase/models/Showkase.kt b/showkase-models/src/main/java/com/airbnb/android/showkase/models/Showkase.kt similarity index 100% rename from showkase/src/main/java/com/airbnb/android/showkase/models/Showkase.kt rename to showkase-models/src/main/java/com/airbnb/android/showkase/models/Showkase.kt diff --git a/showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserColor.kt b/showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserColor.kt similarity index 100% rename from showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserColor.kt rename to showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserColor.kt diff --git a/showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserComponent.kt b/showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserComponent.kt similarity index 79% rename from showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserComponent.kt rename to showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserComponent.kt index 5dfc70259..3a11da2fb 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserComponent.kt +++ b/showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserComponent.kt @@ -3,8 +3,6 @@ package com.airbnb.android.showkase.models import androidx.compose.runtime.Composable import com.airbnb.android.showkase.annotation.ScreenshotConfig -// TODO(vinaygaba): Move it to a different module that has Android/Compose dependencies hoooked up. -// This was added here only because this module has compose dependencies. data class ShowkaseBrowserComponent( val componentKey: String, val group: String, @@ -18,4 +16,7 @@ data class ShowkaseBrowserComponent( val tags: List = emptyList(), val extraMetadata: List = emptyList(), val screenshotConfig: ScreenshotConfig = ScreenshotConfig.SingleStaticImage, + val isDialog: Boolean = false, + val dialogButtonText: String = "", + val dialogHideButtonText: String = "", ) diff --git a/showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserTypography.kt b/showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserTypography.kt similarity index 100% rename from showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserTypography.kt rename to showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseBrowserTypography.kt diff --git a/showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseCategory.kt b/showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseCategory.kt similarity index 100% rename from showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseCategory.kt rename to showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseCategory.kt diff --git a/showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseElementsMetadata.kt b/showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseElementsMetadata.kt similarity index 100% rename from showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseElementsMetadata.kt rename to showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseElementsMetadata.kt diff --git a/showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseProvider.kt b/showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseProvider.kt similarity index 89% rename from showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseProvider.kt rename to showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseProvider.kt index 4c47edbe2..6a9b2bd52 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/models/ShowkaseProvider.kt +++ b/showkase-models/src/main/java/com/airbnb/android/showkase/models/ShowkaseProvider.kt @@ -1,9 +1,12 @@ package com.airbnb.android.showkase.models +import androidx.annotation.RestrictTo + /** * Interface implemented by the final class that's autogenerated by the ShowkaseProcessor. User's * of this library don't need to use this as it's for internal usage only. */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface ShowkaseProvider { fun getShowkaseComponents(): List diff --git a/showkase-processor-testing/.gitignore b/showkase-processor-testing/.gitignore deleted file mode 100644 index 796b96d1c..000000000 --- a/showkase-processor-testing/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/showkase-processor-testing/build.gradle b/showkase-processor-testing/build.gradle deleted file mode 100644 index 1095f4a59..000000000 --- a/showkase-processor-testing/build.gradle +++ /dev/null @@ -1,102 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'org.jetbrains.kotlin.plugin.compose' - -android { - namespace "com.airbnb.android.showkase_processor_testing" - // Added to avoid this error - - // Execution failed for task ':showkase-processor-testing:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade - // > More than one file was found with OS independent path 'META-INF/gradle/incremental.annotation.processors' - packagingOptions { - exclude 'META-INF/gradle/incremental.annotation.processors' - exclude("META-INF/*.kotlin_module") - // Added to avoid this error - - // Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction - // > 2 files found with path 'META-INF/AL2.0' from inputs: - exclude 'META-INF/AL2.0' - exclude 'META-INF/LGPL2.1' - } - defaultConfig { - minSdkVersion 26 - compileSdk 36 - targetSdkVersion 33 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - // The following argument makes the Android Test Orchestrator run its - // "pm clear" command after each test invocation. This command ensures - // that the app's state is completely cleared between tests. - testInstrumentationRunnerArguments clearPackageData: 'true' - } - buildFeatures { - compose true - } - configurations { - all { - // work around this error: - // Duplicate class org.intellij.lang.annotations.Identifier found in modules annotations-12.0 (com.intellij:annotations:12.0) and annotations-13.0 (org.jetbrains:annotations:13.0) - exclude group: "com.intellij", module: "annotations" - } - } -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - // Allows this module to access the annotation processor related classes. Otherwise those are - // only available in java library modules. Inspiration - - // https://github.com/airbnb/epoxy/blob/master/epoxy-processortest/build.gradle - testImplementation files('libs/rt.jar') - - // Support Libraries - implementation deps.support.appCompat - - // Showkase - implementation project(':showkase') - implementation project(':showkase-processor') - implementation project(':showkase-screenshot-testing') - - // KSP - implementation deps.ksp - - // Compose - implementation deps.compose.activityCompose - implementation deps.compose.composeRuntime - implementation deps.compose.core - implementation deps.compose.foundation - implementation deps.compose.tooling - - // Material - implementation deps.material.material - implementation deps.material.mdcComposeThemeAdapter - - // Testing - testImplementation deps.test.assertJ - testImplementation deps.test.googleTruth - testImplementation deps.test.junit - testImplementation deps.kotlinCompileTesting - testImplementation deps.kotlinCompileTestingKsp - testImplementation project(':showkase-screenshot-testing-paparazzi') - testImplementation deps.test.paparazzi -} - -// Needed for Java17 otherwise these tests failed to run locally. -// More info - https://youtrack.jetbrains.com/issue/KT-45545/Kapt-is-not-compatible-with-JDK-16 -tasks.withType(Test) { - if (JavaVersion.current() >= JavaVersion.VERSION_16) { - jvmArgs( - "--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - ) - } -} diff --git a/showkase-processor-testing/build.gradle.kts b/showkase-processor-testing/build.gradle.kts new file mode 100644 index 000000000..2f15bf833 --- /dev/null +++ b/showkase-processor-testing/build.gradle.kts @@ -0,0 +1,66 @@ +plugins { + id("com.android.library") + alias(libs.plugins.kotlin.compose) +} + +android { + namespace = "com.airbnb.android.showkase_processor_testing" + + defaultConfig { + minSdk = 26 + compileSdk = 36 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["clearPackageData"] = "true" + } + + buildFeatures { + compose = true + } + + packaging { + resources { + excludes += listOf( + "META-INF/gradle/incremental.annotation.processors", + "META-INF/*.kotlin_module", + "META-INF/AL2.0", + "META-INF/LGPL2.1", + ) + } + } + lint { + baseline = file("lint-baseline.xml") + } +} + +configurations.all { + exclude(group = "com.intellij", module = "annotations") +} + +kotlin { + jvmToolchain(21) +} + +dependencies { + implementation(libs.support.appCompat) + + implementation(project(":showkase")) + implementation(project(":showkase-processor")) + implementation(project(":showkase-screenshot-testing")) + + implementation(libs.ksp) + + implementation(libs.compose.activityCompose) + implementation(libs.compose.composeRuntime) + implementation(libs.compose.core) + implementation(libs.compose.foundation) + implementation(libs.compose.tooling) + + implementation(libs.material.material) + + testImplementation(libs.test.assertJ) + testImplementation(libs.test.googleTruth) + testImplementation(libs.test.junit) + testImplementation(libs.kotlinCompileTesting) + testImplementation(libs.kotlinCompileTestingKsp) + testImplementation(project(":showkase-screenshot-testing-roborazzi")) +} diff --git a/showkase-processor-testing/libs/rt.jar b/showkase-processor-testing/libs/rt.jar deleted file mode 100644 index e30d53b9c..000000000 Binary files a/showkase-processor-testing/libs/rt.jar and /dev/null differ diff --git a/showkase-processor-testing/lint-baseline.xml b/showkase-processor-testing/lint-baseline.xml new file mode 100644 index 000000000..aced025c1 --- /dev/null +++ b/showkase-processor-testing/lint-baseline.xml @@ -0,0 +1,5 @@ + + + + diff --git a/showkase-processor-testing/src/test/java/com/airbnb/android/showkase_processor_testing/BaseProcessorTest.kt b/showkase-processor-testing/src/test/java/com/airbnb/android/showkase_processor_testing/BaseProcessorTest.kt index 3453603d4..d1711b270 100644 --- a/showkase-processor-testing/src/test/java/com/airbnb/android/showkase_processor_testing/BaseProcessorTest.kt +++ b/showkase-processor-testing/src/test/java/com/airbnb/android/showkase_processor_testing/BaseProcessorTest.kt @@ -2,11 +2,9 @@ package com.airbnb.android.showkase_processor_testing -import com.airbnb.android.showkase.processor.ShowkaseProcessor import com.airbnb.android.showkase.processor.ShowkaseProcessorProvider import com.google.common.io.Resources import com.tschuchort.compiletesting.CompilationResult -import com.tschuchort.compiletesting.JvmCompilationResult import com.tschuchort.compiletesting.KotlinCompilation import com.tschuchort.compiletesting.SourceFile import com.tschuchort.compiletesting.configureKsp @@ -27,58 +25,41 @@ abstract class BaseProcessorTest { @JvmField val testNameRule = TestNameRule() - enum class Mode { - KSP, KAPT - } - /** * Collects the files in the "input" directory of this test's resources directory * and compiles them with Kotlin, returning the result. */ @OptIn(ExperimentalCompilerApi::class) protected fun compileInputs( - modes: List = listOf(Mode.KSP, Mode.KAPT), options: MutableMap = mutableMapOf(), - onCompilation: (mode: Mode, compilation: KotlinCompilation, result: CompilationResult) -> Unit + onCompilation: (compilation: KotlinCompilation, result: CompilationResult) -> Unit ) { val testResourcesDir = getTestResourcesDirectory(getRootResourcesDir()) val inputDir = File(testResourcesDir, "input") inputDir.mkdirs() - modes.forEach { mode -> - val compilation = KotlinCompilation().apply { - kotlincArguments = kotlincArguments + "-Xexplicit-api=strict" - sources = inputDir.listFiles()?.toList().orEmpty() - .map { file -> SourceFile.new(file.name, file.readText()) } - when (mode) { - Mode.KSP -> { - languageVersion = "2.1" - configureKsp(useKsp2 = true) { - symbolProcessorProviders.add(ShowkaseProcessorProvider()) - processorOptions.putAll(options) - } - } - - Mode.KAPT -> { - languageVersion = "1.9" - annotationProcessors = listOf(ShowkaseProcessor()) - kaptArgs = options - } - } - inheritClassPath = true - messageOutputStream = System.out // see diagnostics in real time + val compilation = KotlinCompilation().apply { + kotlincArguments = kotlincArguments + "-Xexplicit-api=strict" + sources = inputDir.listFiles()?.toList().orEmpty() + .map { file -> SourceFile.new(file.name, file.readText()) } + languageVersion = "2.1" + configureKsp { + symbolProcessorProviders.add(ShowkaseProcessorProvider()) + processorOptions.putAll(options) } + inheritClassPath = true + messageOutputStream = System.out // see diagnostics in real time + } - val result = compilation.compile() + val result = compilation.compile() - onCompilation(mode, compilation, result) - } + onCompilation(compilation, result) } @OptIn(ExperimentalCompilerApi::class) protected fun assertCompilationFails(errorMessage: String) { - compileInputs { _, _, result -> + compileInputs { _, result -> assertThat(result.exitCode) .isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR) @@ -89,11 +70,10 @@ abstract class BaseProcessorTest { @OptIn(ExperimentalCompilerApi::class) protected fun compileInputsAndVerifyOutputs( - modes: List = listOf(Mode.KSP, Mode.KAPT), options: MutableMap = mutableMapOf(), ) { - compileInputs(modes = modes, options = options) { mode, compilation, result -> - result.assertGeneratedSources(mode, compilation) + compileInputs(options = options) { compilation, result -> + result.assertGeneratedSources(compilation) } } @@ -102,7 +82,7 @@ abstract class BaseProcessorTest { * and validates that they match the generated sources of this compilation result. */ @OptIn(ExperimentalCompilerApi::class) - protected fun CompilationResult.assertGeneratedSources(mode: Mode, compilation: KotlinCompilation) { + protected fun CompilationResult.assertGeneratedSources(compilation: KotlinCompilation) { assertThat(exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) val testResourcesDir = getTestResourcesDirectory(getRootResourcesDir()) @@ -113,10 +93,7 @@ abstract class BaseProcessorTest { } outputDir.mkdirs() - val generatedSources = when (mode) { - Mode.KSP -> compilation.kspSourcesDir.walk().filter { it.isFile }.toList() - Mode.KAPT -> (this as JvmCompilationResult).sourcesGeneratedByAnnotationProcessor - } + val generatedSources = compilation.kspSourcesDir.walk().filter { it.isFile }.toList() if (UPDATE_TEST_OUTPUTS) { generatedSources.forEach { @@ -129,11 +106,16 @@ abstract class BaseProcessorTest { generatedSources.forEach { actualFile -> val expectedFile = File(outputDir, actualFile.name) assertThat(expectedFile).exists() - assertThat(actualFile).hasSameTextualContentAs(expectedFile) + assertThat(actualFile.normalizedText()) + .describedAs("Generated file %s does not match expected output", actualFile.name) + .isEqualTo(expectedFile.normalizedText()) } } } + private fun File.normalizedText(): String = + readText().replace(Regex("\\s+"), "") + private fun getRootResourcesDir(): File { val path = Resources.getResource("") .path diff --git a/showkase-processor-testing/src/test/java/com/airbnb/android/showkase_processor_testing/ShowkaseProcessorTest.kt b/showkase-processor-testing/src/test/java/com/airbnb/android/showkase_processor_testing/ShowkaseProcessorTest.kt index cf5a02b09..c5eb14bb8 100644 --- a/showkase-processor-testing/src/test/java/com/airbnb/android/showkase_processor_testing/ShowkaseProcessorTest.kt +++ b/showkase-processor-testing/src/test/java/com/airbnb/android/showkase_processor_testing/ShowkaseProcessorTest.kt @@ -198,7 +198,7 @@ class ShowkaseProcessorTest : BaseProcessorTest() { @Test fun `open class with no interface but ShowkaseScreenshoTest annotation throws compilation error`() { assertCompilationFails( - "Only an implementation of com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotTest or com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest can be annotated with @ShowkaseScreenshot" + "Only an implementation of com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotTest or com.airbnb.android.showkase.screenshot.testing.roborazzi.RoborazziShowkaseScreenshotTest can be annotated with @ShowkaseScreenshot" ) } @@ -207,21 +207,6 @@ class ShowkaseProcessorTest : BaseProcessorTest() { assertCompilationFails("Class annotated with ShowkaseScreenshot needs to be an abstract/open class") } - @Test - fun `closed class with PaparazziShowkaseScreenshotTest and ShowkaseScreensho annotation throws compilation error`() { - assertCompilationFails("Class annotated with ShowkaseScreenshot needs to be an abstract/open class") - } - - @Test - fun `class implementing PaparazziShowkaseScreenshotTest but not companion object throws compilation error`() { - assertCompilationFails("Classes implementing the com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest interface should have a companion object that implements the com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest.CompanionObject interface") - } - - @Test - fun `class implementing PaparazziShowkaseScreenshotTest and companion object implementing different interface throws compilation error`() { - assertCompilationFails("Classes implementing the com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest interface should have a companion object that implements the com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest.CompanionObject interface") - } - @Test fun `top level composable function with showkase annotation generates only metadata file`() { compileInputsAndVerifyOutputs() @@ -517,21 +502,11 @@ class ShowkaseProcessorTest : BaseProcessorTest() { compileInputsAndVerifyOutputs() } - @Test - fun `top level composable and class with @ScreenshotTest generates Paparazzi screenshot test for composable`() { - compileInputsAndVerifyOutputs() - } - @Test fun `top level color and class with @ScreenshotTest generates screenshot test for composable`() { compileInputsAndVerifyOutputs() } - @Test - fun `top level color and class with @ScreenshotTest generates paparazzi screenshot test for composable`() { - compileInputsAndVerifyOutputs() - } - @Test fun `top level textstyle and class with @ScreenshotTest generates screenshot test for composable`() { compileInputsAndVerifyOutputs() @@ -547,58 +522,39 @@ class ShowkaseProcessorTest : BaseProcessorTest() { compileInputsAndVerifyOutputs() } - @Test - fun `class with @ScreenshotTest generates paparazzi screenshot test for all UI elements`() { - compileInputsAndVerifyOutputs() - } - - @Test - fun `composable function with multiple preview functions compiles`() { - // Testing only KAPT here since some of these previews are stacked. - // This is yielding different output as repeatable annotations - // are not yet supported by KAPT. - compileInputsAndVerifyOutputs(modes = listOf(Mode.KAPT)) - } - @Test fun `composable function with multiple preview functions compiles ksp`() { - compileInputsAndVerifyOutputs(modes = listOf(Mode.KSP)) + compileInputsAndVerifyOutputs() } @Test fun `composable function with multiple preview annotations stacked generates output`() { - // This functionality is only supported with KSP for now. - compileInputsAndVerifyOutputs(modes = listOf(Mode.KSP)) + compileInputsAndVerifyOutputs() } @Test fun `composable function with multiple showkasecomposable annotations stacked generates output`() { - // This functionality is only supported with KSP for now. - compileInputsAndVerifyOutputs(modes = listOf(Mode.KSP)) + compileInputsAndVerifyOutputs() } @Test fun `composable function with custom preview annotation generates output`() { - compileInputsAndVerifyOutputs(modes = listOf(Mode.KAPT, Mode.KSP)) + compileInputsAndVerifyOutputs() } @Test fun `composable function with custom preview annotation with preview param generates output`() { - compileInputsAndVerifyOutputs(modes = listOf(Mode.KAPT, Mode.KSP)) + compileInputsAndVerifyOutputs() } @Test fun `composable function with repeatable custom preview annotation generates output`() { - // This is only supported by KSP for now - compileInputsAndVerifyOutputs(modes = listOf(Mode.KSP)) + compileInputsAndVerifyOutputs() } @Test fun `composable function with multiple repeatable custom preview annotation generates output`() { - // This is only supported by KSP for now - compileInputsAndVerifyOutputs( - modes = listOf(Mode.KSP), - ) + compileInputsAndVerifyOutputs() } @Test diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_and_companion_object_implementing_different_interface_throws_compilation_error/input/MyShowkaseScreenshotTest.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_and_companion_object_implementing_different_interface_throws_compilation_error/input/MyShowkaseScreenshotTest.kt deleted file mode 100644 index 8b33e122c..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_and_companion_object_implementing_different_interface_throws_compilation_error/input/MyShowkaseScreenshotTest.kt +++ /dev/null @@ -1,8 +0,0 @@ - -import android.graphics.Bitmap -import com.airbnb.android.showkase.annotation.ShowkaseScreenshot -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest - -@ShowkaseScreenshot(rootShowkaseClass = TestShowkaseRoot::class) -public abstract class MyScreenshotTest: PaparazziShowkaseScreenshotTest { -} \ No newline at end of file diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_and_companion_object_implementing_different_interface_throws_compilation_error/input/TestShowkaseRoot.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_and_companion_object_implementing_different_interface_throws_compilation_error/input/TestShowkaseRoot.kt deleted file mode 100644 index 0a6e19e20..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_and_companion_object_implementing_different_interface_throws_compilation_error/input/TestShowkaseRoot.kt +++ /dev/null @@ -1,8 +0,0 @@ - -import com.airbnb.android.showkase.annotation.ShowkaseRoot -import com.airbnb.android.showkase.annotation.ShowkaseRootModule - -@ShowkaseRoot -public class TestShowkaseRoot: ShowkaseRootModule { - -} \ No newline at end of file diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_and_companion_object_implementing_different_interface_throws_compilation_error/input/testComposables.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_and_companion_object_implementing_different_interface_throws_compilation_error/input/testComposables.kt deleted file mode 100644 index 43bce025e..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_and_companion_object_implementing_different_interface_throws_compilation_error/input/testComposables.kt +++ /dev/null @@ -1,28 +0,0 @@ - -import androidx.compose.runtime.Composable -import com.airbnb.android.showkase.annotation.ShowkaseColor -import com.airbnb.android.showkase.annotation.ShowkaseComposable -import com.airbnb.android.showkase.annotation.ShowkaseTypography -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily - -@ShowkaseComposable(name = "name1", group = "group1") -@Composable -public fun TestComposable1() { - -} - -@ShowkaseComposable(name = "name2", group = "group2") -@Composable -public fun TestComposable2() { - -} - -@ShowkaseColor("name", "color") -public val red: Color = Color(0xffff0000) - -@ShowkaseTypography("name", "typography") -public val title: TextStyle = TextStyle( - fontFamily = FontFamily.Cursive -) diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_but_not_companion_object_throws_compilation_error/input/MyShowkaseScreenshotTest.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_but_not_companion_object_throws_compilation_error/input/MyShowkaseScreenshotTest.kt deleted file mode 100644 index aa76d7eaf..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_but_not_companion_object_throws_compilation_error/input/MyShowkaseScreenshotTest.kt +++ /dev/null @@ -1,7 +0,0 @@ -import android.graphics.Bitmap -import com.airbnb.android.showkase.annotation.ShowkaseScreenshot -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest - -@ShowkaseScreenshot(rootShowkaseClass = TestShowkaseRoot::class) -public abstract class MyScreenshotTest: PaparazziShowkaseScreenshotTest { -} \ No newline at end of file diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_but_not_companion_object_throws_compilation_error/input/TestShowkaseRoot.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_but_not_companion_object_throws_compilation_error/input/TestShowkaseRoot.kt deleted file mode 100644 index 2f38f013d..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_but_not_companion_object_throws_compilation_error/input/TestShowkaseRoot.kt +++ /dev/null @@ -1,9 +0,0 @@ -import com.airbnb.android.showkase.annotation.ShowkaseComposable -import androidx.compose.runtime.Composable -import com.airbnb.android.showkase.annotation.ShowkaseRoot -import com.airbnb.android.showkase.annotation.ShowkaseRootModule - -@ShowkaseRoot -public class TestShowkaseRoot: ShowkaseRootModule { - -} \ No newline at end of file diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_but_not_companion_object_throws_compilation_error/input/testComposables.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_but_not_companion_object_throws_compilation_error/input/testComposables.kt deleted file mode 100644 index 3b9e96b00..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_implementing_PaparazziShowkaseScreenshotTest_but_not_companion_object_throws_compilation_error/input/testComposables.kt +++ /dev/null @@ -1,27 +0,0 @@ -import androidx.compose.runtime.Composable -import com.airbnb.android.showkase.annotation.ShowkaseColor -import com.airbnb.android.showkase.annotation.ShowkaseComposable -import com.airbnb.android.showkase.annotation.ShowkaseTypography -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily - -@ShowkaseComposable(name = "name1", group = "group1") -@Composable -public fun TestComposable1() { - -} - -@ShowkaseComposable(name = "name2", group = "group2") -@Composable -public fun TestComposable2() { - -} - -@ShowkaseColor("name", "color") -public val red: Color = Color(0xffff0000) - -@ShowkaseTypography("name", "typography") -public val title: TextStyle = TextStyle( - fontFamily = FontFamily.Cursive -) diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/input/MyShowkaseScreenshotTest.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/input/MyShowkaseScreenshotTest.kt deleted file mode 100644 index 4bd35c4b2..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/input/MyShowkaseScreenshotTest.kt +++ /dev/null @@ -1,11 +0,0 @@ - -import android.graphics.Bitmap -import com.airbnb.android.showkase.annotation.ShowkaseScreenshot -import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotTest -import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotType -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest - -@ShowkaseScreenshot(rootShowkaseClass = TestShowkaseRoot::class) -public abstract class MyScreenshotTest: PaparazziShowkaseScreenshotTest { - public companion object: PaparazziShowkaseScreenshotTest.CompanionObject -} \ No newline at end of file diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/input/TestShowkaseRoot.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/input/TestShowkaseRoot.kt deleted file mode 100644 index 081dd58df..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/input/TestShowkaseRoot.kt +++ /dev/null @@ -1,10 +0,0 @@ - -import com.airbnb.android.showkase.annotation.ShowkaseComposable -import androidx.compose.runtime.Composable -import com.airbnb.android.showkase.annotation.ShowkaseRoot -import com.airbnb.android.showkase.annotation.ShowkaseRootModule - -@ShowkaseRoot -public class TestShowkaseRoot: ShowkaseRootModule { - -} \ No newline at end of file diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/input/testComposables.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/input/testComposables.kt deleted file mode 100644 index 43bce025e..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/input/testComposables.kt +++ /dev/null @@ -1,28 +0,0 @@ - -import androidx.compose.runtime.Composable -import com.airbnb.android.showkase.annotation.ShowkaseColor -import com.airbnb.android.showkase.annotation.ShowkaseComposable -import com.airbnb.android.showkase.annotation.ShowkaseTypography -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily - -@ShowkaseComposable(name = "name1", group = "group1") -@Composable -public fun TestComposable1() { - -} - -@ShowkaseComposable(name = "name2", group = "group2") -@Composable -public fun TestComposable2() { - -} - -@ShowkaseColor("name", "color") -public val red: Color = Color(0xffff0000) - -@ShowkaseTypography("name", "typography") -public val title: TextStyle = TextStyle( - fontFamily = FontFamily.Cursive -) diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/MyScreenshotTestImpl.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/MyScreenshotTestImpl.kt deleted file mode 100644 index 30f25e8bf..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/MyScreenshotTestImpl.kt +++ /dev/null @@ -1,67 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import androidx.compose.ui.unit.LayoutDirection -import app.cash.paparazzi.Paparazzi -import com.airbnb.android.showkase.models.Showkase -import com.airbnb.android.showkase.screenshot.testing.paparazzi.ColorPaparazziShowkaseTestPreview -import com.airbnb.android.showkase.screenshot.testing.paparazzi.ComponentPaparazziShowkaseTestPreview -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseDeviceConfig -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseTestPreview -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseUIMode -import com.airbnb.android.showkase.screenshot.testing.paparazzi.TypographyPaparazziShowkaseTestPreview -import com.google.testing.junit.testparameterinjector.TestParameter -import com.google.testing.junit.testparameterinjector.TestParameter.TestParameterValuesProvider -import com.google.testing.junit.testparameterinjector.TestParameterInjector -import getMetadata -import kotlin.Suppress -import kotlin.collections.List -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(TestParameterInjector::class) -public class MyScreenshotTestImpl : MyScreenshotTest() { - @get:Rule - public val paparazzi: Paparazzi = providePaparazzi() - - @Test - public fun test_previews( - @TestParameter(valuesProvider = PaparazziShowkasePreviewProvider::class) - elementPreview: PaparazziShowkaseTestPreview, - @TestParameter(valuesProvider = PaparazziShowkaseDeviceConfigProvider::class) - config: PaparazziShowkaseDeviceConfig, - @TestParameter(valuesProvider = PaparazziShowkaseLayoutDirectionProvider::class) - direction: LayoutDirection, - @TestParameter(valuesProvider = PaparazziShowkaseUIModeProvider::class) - uiMode: PaparazziShowkaseUIMode, - ) { - paparazzi.unsafeUpdateConfig(config.deviceConfig.copy(softButtons = false)) - takePaparazziSnapshot(paparazzi, elementPreview, direction, uiMode, elementPreview.captureType) - } - - @Suppress("DEPRECATION") - private object PaparazziShowkasePreviewProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List { - val metadata = Showkase.getMetadata() - val components = metadata.componentList.map(::ComponentPaparazziShowkaseTestPreview) - val colors = metadata.colorList.map(::ColorPaparazziShowkaseTestPreview) - val typography = metadata.typographyList.map(::TypographyPaparazziShowkaseTestPreview) - return components + colors + typography - } - } - - @Suppress("DEPRECATION") - private object PaparazziShowkaseDeviceConfigProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List = deviceConfigs() - } - - @Suppress("DEPRECATION") - private object PaparazziShowkaseLayoutDirectionProvider : - TestParameter.TestParameterValuesProvider { - override fun provideValues(): List = layoutDirections() - } - - @Suppress("DEPRECATION") - private object PaparazziShowkaseUIModeProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List = uiModes() - } -} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/ShowkaseMetadata_.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/ShowkaseMetadata_.kt deleted file mode 100644 index 7c1b583e2..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/ShowkaseMetadata_.kt +++ /dev/null @@ -1,68 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -package com.airbnb.android.showkase - -import com.airbnb.android.showkase.`annotation`.ShowkaseCodegenMetadata - -public class ShowkaseMetadata_ { - @ShowkaseCodegenMetadata( - showkaseName = "name1", - showkaseGroup = "group1", - packageName = "", - packageSimpleName = "", - showkaseElementName = "TestComposable1", - insideObject = false, - insideWrapperClass = false, - showkaseKDoc = "", - generatedPropertyName = "TestComposable1group1name1", - showkaseMetadataType = "COMPONENT", - isDefaultStyle = false, - ) - public fun TestComposable1group1name1() { - } - - @ShowkaseCodegenMetadata( - showkaseName = "name2", - showkaseGroup = "group2", - packageName = "", - packageSimpleName = "", - showkaseElementName = "TestComposable2", - insideObject = false, - insideWrapperClass = false, - showkaseKDoc = "", - generatedPropertyName = "TestComposable2group2name2", - showkaseMetadataType = "COMPONENT", - isDefaultStyle = false, - ) - public fun TestComposable2group2name2() { - } - - @ShowkaseCodegenMetadata( - showkaseName = "name", - showkaseGroup = "color", - packageName = "", - packageSimpleName = "", - showkaseElementName = "red", - insideObject = false, - insideWrapperClass = false, - showkaseKDoc = "", - generatedPropertyName = "redcolorname", - showkaseMetadataType = "COLOR", - ) - public fun redcolorname() { - } - - @ShowkaseCodegenMetadata( - showkaseName = "name", - showkaseGroup = "typography", - packageName = "", - packageSimpleName = "", - showkaseElementName = "title", - insideObject = false, - insideWrapperClass = false, - showkaseKDoc = "", - generatedPropertyName = "titletypographyname", - showkaseMetadataType = "TYPOGRAPHY", - ) - public fun titletypographyname() { - } -} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestComposable1group1name1.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestComposable1group1name1.kt deleted file mode 100644 index da1aab0fe..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestComposable1group1name1.kt +++ /dev/null @@ -1,14 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import androidx.compose.runtime.Composable -import com.airbnb.android.showkase.`annotation`.ScreenshotConfig -import com.airbnb.android.showkase.models.ShowkaseBrowserComponent - -public val TestComposable1group1name1: ShowkaseBrowserComponent = ShowkaseBrowserComponent( - group = "group1", - componentName = "name1", - componentKDoc = "", - componentKey = """_TestComposable1_null_group1_name1_0_null""", - isDefaultStyle = false, - screenshotConfig = ScreenshotConfig.SingleStaticImage, - component = @Composable { TestComposable1() } - ) diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestComposable2group2name2.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestComposable2group2name2.kt deleted file mode 100644 index bdff419de..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestComposable2group2name2.kt +++ /dev/null @@ -1,14 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import androidx.compose.runtime.Composable -import com.airbnb.android.showkase.`annotation`.ScreenshotConfig -import com.airbnb.android.showkase.models.ShowkaseBrowserComponent - -public val TestComposable2group2name2: ShowkaseBrowserComponent = ShowkaseBrowserComponent( - group = "group2", - componentName = "name2", - componentKDoc = "", - componentKey = """_TestComposable2_null_group2_name2_0_null""", - isDefaultStyle = false, - screenshotConfig = ScreenshotConfig.SingleStaticImage, - component = @Composable { TestComposable2() } - ) diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestShowkaseRootCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestShowkaseRootCodegen.kt deleted file mode 100644 index 98f654990..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestShowkaseRootCodegen.kt +++ /dev/null @@ -1,37 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import com.airbnb.android.showkase.`annotation`.ShowkaseRootCodegen -import com.airbnb.android.showkase.models.ShowkaseBrowserColor -import com.airbnb.android.showkase.models.ShowkaseBrowserComponent -import com.airbnb.android.showkase.models.ShowkaseBrowserTypography -import com.airbnb.android.showkase.models.ShowkaseProvider -import kotlin.collections.List - -@ShowkaseRootCodegen( - numComposablesWithoutPreviewParameter = 2, - numComposablesWithPreviewParameter = 0, - numColors = 1, - numTypography = 1, -) -public class TestShowkaseRootCodegen : ShowkaseProvider { - override fun getShowkaseComponents(): List { - - return listOf( - TestComposable1group1name1, - TestComposable2group2name2, - ) - } - - override fun getShowkaseColors(): List { - - return listOf( - redcolorname, - ) - } - - override fun getShowkaseTypography(): List { - - return listOf( - titletypographyname, - ) - } -} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt deleted file mode 100644 index 2b6a45a57..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ /dev/null @@ -1,31 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import android.content.Context -import android.content.Intent -import com.airbnb.android.showkase.models.Showkase -import com.airbnb.android.showkase.models.ShowkaseElementsMetadata -import com.airbnb.android.showkase.models.ShowkaseProvider -import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity - -/** - * Helper function that's autogenerated and gives you an intent to start the ShowkaseBrowser. - */ -public fun Showkase.getBrowserIntent(context: Context): Intent { - val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", ".TestShowkaseRoot") - return intent -} - -/** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. - */ -public fun Showkase.getMetadata(): ShowkaseElementsMetadata { - try { - val showkaseComponentProvider = Class.forName(".TestShowkaseRootCodegen") - .getDeclaredConstructor() - .newInstance() as ShowkaseProvider - return showkaseComponentProvider.metadata() - } catch(exception: ClassNotFoundException) { - error("The class wasn't generated correctly. Make sure that you have setup Showkase correctly by following the steps here - https://github.com/airbnb/Showkase#Installation.") - } -} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/redcolorname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/redcolorname.kt deleted file mode 100644 index 3c13537d5..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/redcolorname.kt +++ /dev/null @@ -1,10 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import com.airbnb.android.showkase.models.ShowkaseBrowserColor - -public val redcolorname: ShowkaseBrowserColor = - ShowkaseBrowserColor( - colorGroup = "color", - colorName = "name", - colorKDoc = "", - color = red - ) diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/titletypographyname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/titletypographyname.kt deleted file mode 100644 index f7f5e7d2d..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/titletypographyname.kt +++ /dev/null @@ -1,10 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import com.airbnb.android.showkase.models.ShowkaseBrowserTypography - -public val titletypographyname: ShowkaseBrowserTypography = - ShowkaseBrowserTypography( - typographyGroup = "typography", - typographyName = "name", - typographyKDoc = "", - textStyle = title - ) diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/input/MyShowkaseScreenshotTest.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/input/MyShowkaseScreenshotTest.kt index a5176a8ff..82ad6e2e5 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/input/MyShowkaseScreenshotTest.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/input/MyShowkaseScreenshotTest.kt @@ -1,22 +1,12 @@ package com.airbnb.android.showkase_processor_testing -import android.graphics.Bitmap import com.airbnb.android.showkase.annotation.ShowkaseScreenshot +import com.airbnb.android.showkase.screenshot.testing.ScreenshotMetadata import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotTest -import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotType @ShowkaseScreenshot(rootShowkaseClass = TestShowkaseRoot::class) public abstract class MyScreenshotTest: ShowkaseScreenshotTest { - override fun onScreenshot( - id: String, - name: String, - group: String, - styleName: String?, - tags: List, - extraMetadata: List, - screenshotType: ShowkaseScreenshotType, - screenshotBitmap: Bitmap, - ) { - + override fun onScreenshot(metadata: ScreenshotMetadata) { + } -} \ No newline at end of file +} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestComposable1group1name1.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestComposable1group1name1.kt index 8f3c4bef1..4e2c6a92d 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestComposable1group1name1.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestComposable1group1name1.kt @@ -9,8 +9,7 @@ public val TestComposable1group1name1: ShowkaseBrowserComponent = ShowkaseBrowse group = "group1", componentName = "name1", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable1_null_group1_name1_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable1_null_group1_name1_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable1() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestComposable2group2name2.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestComposable2group2name2.kt index 0e36106c0..90023002c 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestComposable2group2name2.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestComposable2group2name2.kt @@ -9,8 +9,7 @@ public val TestComposable2group2name2: ShowkaseBrowserComponent = ShowkaseBrowse group = "group2", componentName = "name2", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable2_null_group2_name2_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable2_null_group2_name2_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable2() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/input/MyShowkaseScreenshotTest.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/input/MyShowkaseScreenshotTest.kt index 1f9010550..82ad6e2e5 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/input/MyShowkaseScreenshotTest.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/input/MyShowkaseScreenshotTest.kt @@ -1,22 +1,12 @@ package com.airbnb.android.showkase_processor_testing -import android.graphics.Bitmap import com.airbnb.android.showkase.annotation.ShowkaseScreenshot +import com.airbnb.android.showkase.screenshot.testing.ScreenshotMetadata import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotTest -import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotType @ShowkaseScreenshot(rootShowkaseClass = TestShowkaseRoot::class) public abstract class MyScreenshotTest: ShowkaseScreenshotTest { - override fun onScreenshot( - id: String, - name: String, - group: String, - styleName: String?, - tags: List, - extraMetadata: List, - screenshotType: ShowkaseScreenshotType, - screenshotBitmap: Bitmap, - ) { + override fun onScreenshot(metadata: ScreenshotMetadata) { } -} \ No newline at end of file +} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/ShowkaseMetadata_com_airbnb_android_showkase_processor_testing.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/ShowkaseMetadata_com_airbnb_android_showkase_processor_testing.kt index defb6dfae..bfca12b33 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/ShowkaseMetadata_com_airbnb_android_showkase_processor_testing.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/ShowkaseMetadata_com_airbnb_android_showkase_processor_testing.kt @@ -20,8 +20,7 @@ public class ShowkaseMetadata_com_airbnb_android_showkase_processor_testing { showkaseMetadataType = "COMPONENT", isDefaultStyle = false, ) - public - fun comairbnbandroidshowkaseprocessortestingWrapperClassTestComposable1WrapperClassTestComposable1() { + public fun comairbnbandroidshowkaseprocessortestingWrapperClassTestComposable1WrapperClassTestComposable1() { } @ShowkaseCodegenMetadata( diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/TestComposable1WrapperClassTestComposable1.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/TestComposable1WrapperClassTestComposable1.kt index e41b37a5c..8655ef427 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/TestComposable1WrapperClassTestComposable1.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/TestComposable1WrapperClassTestComposable1.kt @@ -10,8 +10,7 @@ public val TestComposable1WrapperClassTestComposable1: ShowkaseBrowserComponent group = "WrapperClass", componentName = "TestComposable1", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing.WrapperClass_TestComposable1_com.airbnb.android.showkase_processor_testing.WrapperClass_WrapperClass_TestComposable1_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing.WrapperClass_TestComposable1_com.airbnb.android.showkase_processor_testing.WrapperClass_WrapperClass_TestComposable1_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/TestComposable2DefaultGroupTestComposable2.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/TestComposable2DefaultGroupTestComposable2.kt index 716a49a62..04a201743 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/TestComposable2DefaultGroupTestComposable2.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/TestComposable2DefaultGroupTestComposable2.kt @@ -16,8 +16,7 @@ public val TestComposable2DefaultGroupTestComposable2: List = group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing.WrapperClass_TestComposable_com.airbnb.android.showkase_processor_testing.WrapperClass_group_name_0_null_$index""", + componentKey = """com.airbnb.android.showkase_processor_testing.WrapperClass_TestComposable_com.airbnb.android.showkase_processor_testing.WrapperClass_group_name_0_null_$index""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot_and_long_parameter_provider_name/output/ShowkaseMetadata_com_airbnb_android_showkase_processor_testing_my_very_long_name.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot_and_long_parameter_provider_name/output/ShowkaseMetadata_com_airbnb_android_showkase_processor_testing_my_very_long_name.kt index c09b43f89..0a937ed80 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot_and_long_parameter_provider_name/output/ShowkaseMetadata_com_airbnb_android_showkase_processor_testing_my_very_long_name.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot_and_long_parameter_provider_name/output/ShowkaseMetadata_com_airbnb_android_showkase_processor_testing_my_very_long_name.kt @@ -19,11 +19,9 @@ public class ShowkaseMetadata_com_airbnb_android_showkase_processor_testing_my_v enclosingClass = [WrapperClass::class], showkaseMetadataType = "COMPONENT", isDefaultStyle = false, - previewParameterClass = - [MyVeryLongPackageNameViewStateSomethingSomethingFunnyStuffProvider::class], + previewParameterClass = [MyVeryLongPackageNameViewStateSomethingSomethingFunnyStuffProvider::class], previewParameterName = "text", ) - public - fun comairbnbandroidshowkaseprocessortestingmyverylongnameWrapperClassTestComposablegroupname() { + public fun comairbnbandroidshowkaseprocessortestingmyverylongnameWrapperClassTestComposablegroupname() { } } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot_and_long_parameter_provider_name/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot_and_long_parameter_provider_name/output/TestComposablegroupname.kt index 22dc890a5..33b59b7aa 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot_and_long_parameter_provider_name/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot_and_long_parameter_provider_name/output/TestComposablegroupname.kt @@ -16,8 +16,7 @@ public val TestComposablegroupname: List = group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_my_very_long_name.WrapperClass_TestComposable_com.airbnb.android.showkase_processor_testing_my_very_long_name.WrapperClass_group_name_0_null_$index""", + componentKey = """com.airbnb.android.showkase_processor_testing_my_very_long_name.WrapperClass_TestComposable_com.airbnb.android.showkase_processor_testing_my_very_long_name.WrapperClass_group_name_0_null_$index""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot_and_long_parameter_provider_name/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot_and_long_parameter_provider_name/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index 0816887ae..b709be549 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot_and_long_parameter_provider_name/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/object_function_with_preview_annotation_and_preview_parameter_and_showkaseroot_and_long_parameter_provider_name/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing_my_very_long_name.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing_my_very_long_name.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing_my_very_long_name.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing_my_very_long_name.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok/output/ShowkaseMetadata_showkaseprocessortest_private_composable_with_preview_annotation_and_skipprivate_option_compiles_ok_input.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok/output/ShowkaseMetadata_showkaseprocessortest_private_composable_with_preview_annotation_and_skipprivate_option_compiles_ok_input.kt index a070afe35..6c73d18c4 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok/output/ShowkaseMetadata_showkaseprocessortest_private_composable_with_preview_annotation_and_skipprivate_option_compiles_ok_input.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok/output/ShowkaseMetadata_showkaseprocessortest_private_composable_with_preview_annotation_and_skipprivate_option_compiles_ok_input.kt @@ -3,14 +3,11 @@ package com.airbnb.android.showkase import com.airbnb.android.showkase.`annotation`.ShowkaseCodegenMetadata -public class - ShowkaseMetadata_showkaseprocessortest_private_composable_with_preview_annotation_and_skipprivate_option_compiles_ok_input - { +public class ShowkaseMetadata_showkaseprocessortest_private_composable_with_preview_annotation_and_skipprivate_option_compiles_ok_input { @ShowkaseCodegenMetadata( showkaseName = "name1", showkaseGroup = "group1", - packageName = - "ShowkaseProcessorTest.private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok.input", + packageName = "ShowkaseProcessorTest.private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok.input", packageSimpleName = "input", showkaseElementName = "TestComposable1", insideObject = false, @@ -20,15 +17,13 @@ public class showkaseMetadataType = "COMPONENT", isDefaultStyle = false, ) - public - fun ShowkaseProcessorTestprivatecomposablewithpreviewannotationandskipPrivateoptioncompilesokinputTestComposable1group1name1() { + public fun ShowkaseProcessorTestprivatecomposablewithpreviewannotationandskipPrivateoptioncompilesokinputTestComposable1group1name1() { } @ShowkaseCodegenMetadata( showkaseName = "name3", showkaseGroup = "group3", - packageName = - "ShowkaseProcessorTest.private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok.input", + packageName = "ShowkaseProcessorTest.private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok.input", packageSimpleName = "input", showkaseElementName = "TestComposable3", insideObject = false, @@ -38,7 +33,6 @@ public class showkaseMetadataType = "COMPONENT", isDefaultStyle = false, ) - public - fun ShowkaseProcessorTestprivatecomposablewithpreviewannotationandskipPrivateoptioncompilesokinputTestComposable3group3name3() { + public fun ShowkaseProcessorTestprivatecomposablewithpreviewannotationandskipPrivateoptioncompilesokinputTestComposable3group3name3() { } } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable1group1name1.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable1group1name1.kt index 8dc301ed4..55cccb0e9 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable1group1name1.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable1group1name1.kt @@ -9,8 +9,7 @@ public val TestComposable1group1name1: ShowkaseBrowserComponent = ShowkaseBrowse group = "group1", componentName = "name1", componentKDoc = "", - componentKey = - """ShowkaseProcessorTest.private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok.input_TestComposable1_null_group1_name1_0_null""", + componentKey = """ShowkaseProcessorTest.private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok.input_TestComposable1_null_group1_name1_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable1() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable3group3name3.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable3group3name3.kt index 3a347942a..1287b7535 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable3group3name3.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable3group3name3.kt @@ -9,8 +9,7 @@ public val TestComposable3group3name3: ShowkaseBrowserComponent = ShowkaseBrowse group = "group3", componentName = "name3", componentKDoc = "", - componentKey = - """ShowkaseProcessorTest.private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok.input_TestComposable3_null_group3_name3_0_null""", + componentKey = """ShowkaseProcessorTest.private_composable_with_preview_annotation_and_skipPrivate_option_compiles_ok.input_TestComposable3_null_group3_name3_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable3() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok/output/ShowkaseMetadata_showkaseprocessortest_private_composable_with_showkase_annotation_and_skipprivate_option_compiles_ok_input.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok/output/ShowkaseMetadata_showkaseprocessortest_private_composable_with_showkase_annotation_and_skipprivate_option_compiles_ok_input.kt index 3f397931a..09994eec8 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok/output/ShowkaseMetadata_showkaseprocessortest_private_composable_with_showkase_annotation_and_skipprivate_option_compiles_ok_input.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok/output/ShowkaseMetadata_showkaseprocessortest_private_composable_with_showkase_annotation_and_skipprivate_option_compiles_ok_input.kt @@ -4,14 +4,11 @@ package com.airbnb.android.showkase import ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input.Composables import com.airbnb.android.showkase.`annotation`.ShowkaseCodegenMetadata -public class - ShowkaseMetadata_showkaseprocessortest_private_composable_with_showkase_annotation_and_skipprivate_option_compiles_ok_input - { +public class ShowkaseMetadata_showkaseprocessortest_private_composable_with_showkase_annotation_and_skipprivate_option_compiles_ok_input { @ShowkaseCodegenMetadata( showkaseName = "name1", showkaseGroup = "group1", - packageName = - "ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input", + packageName = "ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input", packageSimpleName = "input", showkaseElementName = "TestComposable1", insideObject = false, @@ -22,15 +19,13 @@ public class showkaseMetadataType = "COMPONENT", isDefaultStyle = false, ) - public - fun ShowkaseProcessorTestprivatecomposablewithshowkaseannotationandskipPrivateoptioncompilesokinputComposablesTestComposable1group1name1() { + public fun ShowkaseProcessorTestprivatecomposablewithshowkaseannotationandskipPrivateoptioncompilesokinputComposablesTestComposable1group1name1() { } @ShowkaseCodegenMetadata( showkaseName = "name3", showkaseGroup = "group3", - packageName = - "ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input", + packageName = "ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input", packageSimpleName = "input", showkaseElementName = "TestComposable3", insideObject = false, @@ -41,7 +36,6 @@ public class showkaseMetadataType = "COMPONENT", isDefaultStyle = false, ) - public - fun ShowkaseProcessorTestprivatecomposablewithshowkaseannotationandskipPrivateoptioncompilesokinputComposablesTestComposable3group3name3() { + public fun ShowkaseProcessorTestprivatecomposablewithshowkaseannotationandskipPrivateoptioncompilesokinputComposablesTestComposable3group3name3() { } } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable1group1name1.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable1group1name1.kt index 056c3d505..20f359bb8 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable1group1name1.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable1group1name1.kt @@ -9,8 +9,7 @@ public val TestComposable1group1name1: ShowkaseBrowserComponent = ShowkaseBrowse group = "group1", componentName = "name1", componentKDoc = "", - componentKey = - """ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input.Composables_TestComposable1_ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input.Composables_group1_name1_0_null""", + componentKey = """ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input.Composables_TestComposable1_ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input.Composables_group1_name1_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable3group3name3.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable3group3name3.kt index 0ec76f3dc..bb3d36065 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable3group3name3.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok/output/TestComposable3group3name3.kt @@ -9,8 +9,7 @@ public val TestComposable3group3name3: ShowkaseBrowserComponent = ShowkaseBrowse group = "group3", componentName = "name3", componentKDoc = "", - componentKey = - """ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input.Composables_TestComposable3_ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input.Composables_group3_name3_0_null""", + componentKey = """ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input.Composables_TestComposable3_ShowkaseProcessorTest.private_composable_with_showkase_annotation_and_skipPrivate_option_compiles_ok.input.Composables_group3_name3_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/textstyle_property_inside_class_with_showkasetypography_annotation_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/textstyle_property_inside_class_with_showkasetypography_annotation_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/textstyle_property_inside_class_with_showkasetypography_annotation_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/textstyle_property_inside_class_with_showkasetypography_annotation_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/textstyle_property_inside_object_with_showkasetypography_annotation_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/textstyle_property_inside_object_with_showkasetypography_annotation_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/textstyle_property_inside_object_with_showkasetypography_annotation_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/textstyle_property_inside_object_with_showkasetypography_annotation_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/input/GeneratedTestColors.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/input/GeneratedTestColors.kt deleted file mode 100644 index f6318959d..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/input/GeneratedTestColors.kt +++ /dev/null @@ -1,6 +0,0 @@ - -import androidx.compose.ui.graphics.Color -import com.airbnb.android.showkase.annotation.ShowkaseColor - -@ShowkaseColor("name", "group") -public val red: Color = Color(0xffff0000) \ No newline at end of file diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt deleted file mode 100644 index 251eaa2a5..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt +++ /dev/null @@ -1,8 +0,0 @@ - -import com.airbnb.android.showkase.annotation.ShowkaseScreenshot -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest - -@ShowkaseScreenshot(rootShowkaseClass = TestShowkaseRoot::class) -public abstract class MyScreenshotTest: PaparazziShowkaseScreenshotTest { - public companion object: PaparazziShowkaseScreenshotTest.CompanionObject -} \ No newline at end of file diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/input/TestShowkaseRoot.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/input/TestShowkaseRoot.kt deleted file mode 100644 index 0a6e19e20..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/input/TestShowkaseRoot.kt +++ /dev/null @@ -1,8 +0,0 @@ - -import com.airbnb.android.showkase.annotation.ShowkaseRoot -import com.airbnb.android.showkase.annotation.ShowkaseRootModule - -@ShowkaseRoot -public class TestShowkaseRoot: ShowkaseRootModule { - -} \ No newline at end of file diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/MyScreenshotTestImpl.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/MyScreenshotTestImpl.kt deleted file mode 100644 index 30f25e8bf..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/MyScreenshotTestImpl.kt +++ /dev/null @@ -1,67 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import androidx.compose.ui.unit.LayoutDirection -import app.cash.paparazzi.Paparazzi -import com.airbnb.android.showkase.models.Showkase -import com.airbnb.android.showkase.screenshot.testing.paparazzi.ColorPaparazziShowkaseTestPreview -import com.airbnb.android.showkase.screenshot.testing.paparazzi.ComponentPaparazziShowkaseTestPreview -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseDeviceConfig -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseTestPreview -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseUIMode -import com.airbnb.android.showkase.screenshot.testing.paparazzi.TypographyPaparazziShowkaseTestPreview -import com.google.testing.junit.testparameterinjector.TestParameter -import com.google.testing.junit.testparameterinjector.TestParameter.TestParameterValuesProvider -import com.google.testing.junit.testparameterinjector.TestParameterInjector -import getMetadata -import kotlin.Suppress -import kotlin.collections.List -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(TestParameterInjector::class) -public class MyScreenshotTestImpl : MyScreenshotTest() { - @get:Rule - public val paparazzi: Paparazzi = providePaparazzi() - - @Test - public fun test_previews( - @TestParameter(valuesProvider = PaparazziShowkasePreviewProvider::class) - elementPreview: PaparazziShowkaseTestPreview, - @TestParameter(valuesProvider = PaparazziShowkaseDeviceConfigProvider::class) - config: PaparazziShowkaseDeviceConfig, - @TestParameter(valuesProvider = PaparazziShowkaseLayoutDirectionProvider::class) - direction: LayoutDirection, - @TestParameter(valuesProvider = PaparazziShowkaseUIModeProvider::class) - uiMode: PaparazziShowkaseUIMode, - ) { - paparazzi.unsafeUpdateConfig(config.deviceConfig.copy(softButtons = false)) - takePaparazziSnapshot(paparazzi, elementPreview, direction, uiMode, elementPreview.captureType) - } - - @Suppress("DEPRECATION") - private object PaparazziShowkasePreviewProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List { - val metadata = Showkase.getMetadata() - val components = metadata.componentList.map(::ComponentPaparazziShowkaseTestPreview) - val colors = metadata.colorList.map(::ColorPaparazziShowkaseTestPreview) - val typography = metadata.typographyList.map(::TypographyPaparazziShowkaseTestPreview) - return components + colors + typography - } - } - - @Suppress("DEPRECATION") - private object PaparazziShowkaseDeviceConfigProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List = deviceConfigs() - } - - @Suppress("DEPRECATION") - private object PaparazziShowkaseLayoutDirectionProvider : - TestParameter.TestParameterValuesProvider { - override fun provideValues(): List = layoutDirections() - } - - @Suppress("DEPRECATION") - private object PaparazziShowkaseUIModeProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List = uiModes() - } -} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/ShowkaseMetadata_.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/ShowkaseMetadata_.kt deleted file mode 100644 index dbc606d4a..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/ShowkaseMetadata_.kt +++ /dev/null @@ -1,21 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -package com.airbnb.android.showkase - -import com.airbnb.android.showkase.`annotation`.ShowkaseCodegenMetadata - -public class ShowkaseMetadata_ { - @ShowkaseCodegenMetadata( - showkaseName = "name", - showkaseGroup = "group", - packageName = "", - packageSimpleName = "", - showkaseElementName = "red", - insideObject = false, - insideWrapperClass = false, - showkaseKDoc = "", - generatedPropertyName = "redgroupname", - showkaseMetadataType = "COLOR", - ) - public fun redgroupname() { - } -} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/TestShowkaseRootCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/TestShowkaseRootCodegen.kt deleted file mode 100644 index 5cab7e27e..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/TestShowkaseRootCodegen.kt +++ /dev/null @@ -1,34 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import com.airbnb.android.showkase.`annotation`.ShowkaseRootCodegen -import com.airbnb.android.showkase.models.ShowkaseBrowserColor -import com.airbnb.android.showkase.models.ShowkaseBrowserComponent -import com.airbnb.android.showkase.models.ShowkaseBrowserTypography -import com.airbnb.android.showkase.models.ShowkaseProvider -import kotlin.collections.List - -@ShowkaseRootCodegen( - numComposablesWithoutPreviewParameter = 0, - numComposablesWithPreviewParameter = 0, - numColors = 1, - numTypography = 0, -) -public class TestShowkaseRootCodegen : ShowkaseProvider { - override fun getShowkaseComponents(): List { - - return listOf( - ) - } - - override fun getShowkaseColors(): List { - - return listOf( - redgroupname, - ) - } - - override fun getShowkaseTypography(): List { - - return listOf( - ) - } -} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt deleted file mode 100644 index 2b6a45a57..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ /dev/null @@ -1,31 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import android.content.Context -import android.content.Intent -import com.airbnb.android.showkase.models.Showkase -import com.airbnb.android.showkase.models.ShowkaseElementsMetadata -import com.airbnb.android.showkase.models.ShowkaseProvider -import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity - -/** - * Helper function that's autogenerated and gives you an intent to start the ShowkaseBrowser. - */ -public fun Showkase.getBrowserIntent(context: Context): Intent { - val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", ".TestShowkaseRoot") - return intent -} - -/** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. - */ -public fun Showkase.getMetadata(): ShowkaseElementsMetadata { - try { - val showkaseComponentProvider = Class.forName(".TestShowkaseRootCodegen") - .getDeclaredConstructor() - .newInstance() as ShowkaseProvider - return showkaseComponentProvider.metadata() - } catch(exception: ClassNotFoundException) { - error("The class wasn't generated correctly. Make sure that you have setup Showkase correctly by following the steps here - https://github.com/airbnb/Showkase#Installation.") - } -} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/redgroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/redgroupname.kt deleted file mode 100644 index 9e1a3438f..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_composable/output/redgroupname.kt +++ /dev/null @@ -1,10 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import com.airbnb.android.showkase.models.ShowkaseBrowserColor - -public val redgroupname: ShowkaseBrowserColor = - ShowkaseBrowserColor( - colorGroup = "group", - colorName = "name", - colorKDoc = "", - color = red - ) diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt index a5176a8ff..82ad6e2e5 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt @@ -1,22 +1,12 @@ package com.airbnb.android.showkase_processor_testing -import android.graphics.Bitmap import com.airbnb.android.showkase.annotation.ShowkaseScreenshot +import com.airbnb.android.showkase.screenshot.testing.ScreenshotMetadata import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotTest -import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotType @ShowkaseScreenshot(rootShowkaseClass = TestShowkaseRoot::class) public abstract class MyScreenshotTest: ShowkaseScreenshotTest { - override fun onScreenshot( - id: String, - name: String, - group: String, - styleName: String?, - tags: List, - extraMetadata: List, - screenshotType: ShowkaseScreenshotType, - screenshotBitmap: Bitmap, - ) { - + override fun onScreenshot(metadata: ScreenshotMetadata) { + } -} \ No newline at end of file +} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_property_and_composable_function_generates_1_file/output/TestComposablecomponentname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_property_and_composable_function_generates_1_file/output/TestComposablecomponentname.kt index 18f7eeb3b..cb5d90a99 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_property_and_composable_function_generates_1_file/output/TestComposablecomponentname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_property_and_composable_function_generates_1_file/output/TestComposablecomponentname.kt @@ -9,8 +9,7 @@ public val TestComposablecomponentname: ShowkaseBrowserComponent = ShowkaseBrows group = "component", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_component_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_component_name_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_property_and_composable_function_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_property_and_composable_function_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_property_and_composable_function_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_property_and_composable_function_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_property_with_showkasecolor_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_property_with_showkasecolor_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_property_with_showkasecolor_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_color_property_with_showkasecolor_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt deleted file mode 100644 index 9a622cac2..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt +++ /dev/null @@ -1,7 +0,0 @@ -import com.airbnb.android.showkase.annotation.ShowkaseScreenshot -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest - -@ShowkaseScreenshot(rootShowkaseClass = TestShowkaseRoot::class) -public abstract class MyPaparazziScreenshotTest: PaparazziShowkaseScreenshotTest { - public companion object: PaparazziShowkaseScreenshotTest.CompanionObject -} \ No newline at end of file diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/input/TestShowkaseRoot.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/input/TestShowkaseRoot.kt deleted file mode 100644 index 2f38f013d..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/input/TestShowkaseRoot.kt +++ /dev/null @@ -1,9 +0,0 @@ -import com.airbnb.android.showkase.annotation.ShowkaseComposable -import androidx.compose.runtime.Composable -import com.airbnb.android.showkase.annotation.ShowkaseRoot -import com.airbnb.android.showkase.annotation.ShowkaseRootModule - -@ShowkaseRoot -public class TestShowkaseRoot: ShowkaseRootModule { - -} \ No newline at end of file diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/input/testComposables.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/input/testComposables.kt deleted file mode 100644 index eb3ee3165..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/input/testComposables.kt +++ /dev/null @@ -1,14 +0,0 @@ -import androidx.compose.runtime.Composable -import com.airbnb.android.showkase.annotation.ShowkaseComposable - -@ShowkaseComposable(name= "name1", group = "group1") -@Composable -public fun TestComposable1() { - -} - -@ShowkaseComposable(name= "name2", group = "group2") -@Composable -public fun TestComposable2() { - -} \ No newline at end of file diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/MyPaparazziScreenshotTestImpl.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/MyPaparazziScreenshotTestImpl.kt deleted file mode 100644 index 41f0d07a8..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/MyPaparazziScreenshotTestImpl.kt +++ /dev/null @@ -1,67 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import androidx.compose.ui.unit.LayoutDirection -import app.cash.paparazzi.Paparazzi -import com.airbnb.android.showkase.models.Showkase -import com.airbnb.android.showkase.screenshot.testing.paparazzi.ColorPaparazziShowkaseTestPreview -import com.airbnb.android.showkase.screenshot.testing.paparazzi.ComponentPaparazziShowkaseTestPreview -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseDeviceConfig -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseTestPreview -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseUIMode -import com.airbnb.android.showkase.screenshot.testing.paparazzi.TypographyPaparazziShowkaseTestPreview -import com.google.testing.junit.testparameterinjector.TestParameter -import com.google.testing.junit.testparameterinjector.TestParameter.TestParameterValuesProvider -import com.google.testing.junit.testparameterinjector.TestParameterInjector -import getMetadata -import kotlin.Suppress -import kotlin.collections.List -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(TestParameterInjector::class) -public class MyPaparazziScreenshotTestImpl : MyPaparazziScreenshotTest() { - @get:Rule - public val paparazzi: Paparazzi = providePaparazzi() - - @Test - public fun test_previews( - @TestParameter(valuesProvider = PaparazziShowkasePreviewProvider::class) - elementPreview: PaparazziShowkaseTestPreview, - @TestParameter(valuesProvider = PaparazziShowkaseDeviceConfigProvider::class) - config: PaparazziShowkaseDeviceConfig, - @TestParameter(valuesProvider = PaparazziShowkaseLayoutDirectionProvider::class) - direction: LayoutDirection, - @TestParameter(valuesProvider = PaparazziShowkaseUIModeProvider::class) - uiMode: PaparazziShowkaseUIMode, - ) { - paparazzi.unsafeUpdateConfig(config.deviceConfig.copy(softButtons = false)) - takePaparazziSnapshot(paparazzi, elementPreview, direction, uiMode, elementPreview.captureType) - } - - @Suppress("DEPRECATION") - private object PaparazziShowkasePreviewProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List { - val metadata = Showkase.getMetadata() - val components = metadata.componentList.map(::ComponentPaparazziShowkaseTestPreview) - val colors = metadata.colorList.map(::ColorPaparazziShowkaseTestPreview) - val typography = metadata.typographyList.map(::TypographyPaparazziShowkaseTestPreview) - return components + colors + typography - } - } - - @Suppress("DEPRECATION") - private object PaparazziShowkaseDeviceConfigProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List = deviceConfigs() - } - - @Suppress("DEPRECATION") - private object PaparazziShowkaseLayoutDirectionProvider : - TestParameter.TestParameterValuesProvider { - override fun provideValues(): List = layoutDirections() - } - - @Suppress("DEPRECATION") - private object PaparazziShowkaseUIModeProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List = uiModes() - } -} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/ShowkaseMetadata_.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/ShowkaseMetadata_.kt deleted file mode 100644 index 64a55b143..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/ShowkaseMetadata_.kt +++ /dev/null @@ -1,38 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -package com.airbnb.android.showkase - -import com.airbnb.android.showkase.`annotation`.ShowkaseCodegenMetadata - -public class ShowkaseMetadata_ { - @ShowkaseCodegenMetadata( - showkaseName = "name1", - showkaseGroup = "group1", - packageName = "", - packageSimpleName = "", - showkaseElementName = "TestComposable1", - insideObject = false, - insideWrapperClass = false, - showkaseKDoc = "", - generatedPropertyName = "TestComposable1group1name1", - showkaseMetadataType = "COMPONENT", - isDefaultStyle = false, - ) - public fun TestComposable1group1name1() { - } - - @ShowkaseCodegenMetadata( - showkaseName = "name2", - showkaseGroup = "group2", - packageName = "", - packageSimpleName = "", - showkaseElementName = "TestComposable2", - insideObject = false, - insideWrapperClass = false, - showkaseKDoc = "", - generatedPropertyName = "TestComposable2group2name2", - showkaseMetadataType = "COMPONENT", - isDefaultStyle = false, - ) - public fun TestComposable2group2name2() { - } -} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/TestComposable1group1name1.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/TestComposable1group1name1.kt deleted file mode 100644 index da1aab0fe..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/TestComposable1group1name1.kt +++ /dev/null @@ -1,14 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import androidx.compose.runtime.Composable -import com.airbnb.android.showkase.`annotation`.ScreenshotConfig -import com.airbnb.android.showkase.models.ShowkaseBrowserComponent - -public val TestComposable1group1name1: ShowkaseBrowserComponent = ShowkaseBrowserComponent( - group = "group1", - componentName = "name1", - componentKDoc = "", - componentKey = """_TestComposable1_null_group1_name1_0_null""", - isDefaultStyle = false, - screenshotConfig = ScreenshotConfig.SingleStaticImage, - component = @Composable { TestComposable1() } - ) diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/TestComposable2group2name2.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/TestComposable2group2name2.kt deleted file mode 100644 index bdff419de..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/TestComposable2group2name2.kt +++ /dev/null @@ -1,14 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import androidx.compose.runtime.Composable -import com.airbnb.android.showkase.`annotation`.ScreenshotConfig -import com.airbnb.android.showkase.models.ShowkaseBrowserComponent - -public val TestComposable2group2name2: ShowkaseBrowserComponent = ShowkaseBrowserComponent( - group = "group2", - componentName = "name2", - componentKDoc = "", - componentKey = """_TestComposable2_null_group2_name2_0_null""", - isDefaultStyle = false, - screenshotConfig = ScreenshotConfig.SingleStaticImage, - component = @Composable { TestComposable2() } - ) diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/TestShowkaseRootCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/TestShowkaseRootCodegen.kt deleted file mode 100644 index ec49d66c0..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/TestShowkaseRootCodegen.kt +++ /dev/null @@ -1,35 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import com.airbnb.android.showkase.`annotation`.ShowkaseRootCodegen -import com.airbnb.android.showkase.models.ShowkaseBrowserColor -import com.airbnb.android.showkase.models.ShowkaseBrowserComponent -import com.airbnb.android.showkase.models.ShowkaseBrowserTypography -import com.airbnb.android.showkase.models.ShowkaseProvider -import kotlin.collections.List - -@ShowkaseRootCodegen( - numComposablesWithoutPreviewParameter = 2, - numComposablesWithPreviewParameter = 0, - numColors = 0, - numTypography = 0, -) -public class TestShowkaseRootCodegen : ShowkaseProvider { - override fun getShowkaseComponents(): List { - - return listOf( - TestComposable1group1name1, - TestComposable2group2name2, - ) - } - - override fun getShowkaseColors(): List { - - return listOf( - ) - } - - override fun getShowkaseTypography(): List { - - return listOf( - ) - } -} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt deleted file mode 100644 index 2b6a45a57..000000000 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_Paparazzi_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ /dev/null @@ -1,31 +0,0 @@ -// This is an auto-generated file. Please do not edit/modify this file. -import android.content.Context -import android.content.Intent -import com.airbnb.android.showkase.models.Showkase -import com.airbnb.android.showkase.models.ShowkaseElementsMetadata -import com.airbnb.android.showkase.models.ShowkaseProvider -import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity - -/** - * Helper function that's autogenerated and gives you an intent to start the ShowkaseBrowser. - */ -public fun Showkase.getBrowserIntent(context: Context): Intent { - val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", ".TestShowkaseRoot") - return intent -} - -/** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. - */ -public fun Showkase.getMetadata(): ShowkaseElementsMetadata { - try { - val showkaseComponentProvider = Class.forName(".TestShowkaseRootCodegen") - .getDeclaredConstructor() - .newInstance() as ShowkaseProvider - return showkaseComponentProvider.metadata() - } catch(exception: ClassNotFoundException) { - error("The class wasn't generated correctly. Make sure that you have setup Showkase correctly by following the steps here - https://github.com/airbnb/Showkase#Installation.") - } -} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt index a5176a8ff..82ad6e2e5 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/input/MyShowkaseScreenshotTest.kt @@ -1,22 +1,12 @@ package com.airbnb.android.showkase_processor_testing -import android.graphics.Bitmap import com.airbnb.android.showkase.annotation.ShowkaseScreenshot +import com.airbnb.android.showkase.screenshot.testing.ScreenshotMetadata import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotTest -import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotType @ShowkaseScreenshot(rootShowkaseClass = TestShowkaseRoot::class) public abstract class MyScreenshotTest: ShowkaseScreenshotTest { - override fun onScreenshot( - id: String, - name: String, - group: String, - styleName: String?, - tags: List, - extraMetadata: List, - screenshotType: ShowkaseScreenshotType, - screenshotBitmap: Bitmap, - ) { - + override fun onScreenshot(metadata: ScreenshotMetadata) { + } -} \ No newline at end of file +} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestComposable1group1name1.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestComposable1group1name1.kt index 8f3c4bef1..4e2c6a92d 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestComposable1group1name1.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestComposable1group1name1.kt @@ -9,8 +9,7 @@ public val TestComposable1group1name1: ShowkaseBrowserComponent = ShowkaseBrowse group = "group1", componentName = "name1", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable1_null_group1_name1_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable1_null_group1_name1_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable1() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestComposable2group2name2.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestComposable2group2name2.kt index 0e36106c0..90023002c 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestComposable2group2name2.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestComposable2group2name2.kt @@ -9,8 +9,7 @@ public val TestComposable2group2name2: ShowkaseBrowserComponent = ShowkaseBrowse group = "group2", componentName = "name2", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable2_null_group2_name2_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable2_null_group2_name2_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable2() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_generates_1_file/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_generates_1_file/output/TestComposablegroupname.kt index 2f2de10ac..c57ccf75d 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_generates_1_file/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_generates_1_file/output/TestComposablegroupname.kt @@ -9,8 +9,7 @@ public val TestComposablegroupname: ShowkaseBrowserComponent = ShowkaseBrowserCo group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_with_width_and_height/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_with_width_and_height/output/TestComposablegroupname.kt index 26df1f555..3b94c16e1 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_with_width_and_height/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_with_width_and_height/output/TestComposablegroupname.kt @@ -9,8 +9,7 @@ public val TestComposablegroupname: ShowkaseBrowserComponent = ShowkaseBrowserCo group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", isDefaultStyle = false, widthDp = 150, heightDp = 250, diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_with_width_and_height/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_with_width_and_height/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_with_width_and_height/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_and_showkaseroot_with_width_and_height/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_annotation_compiles_ok/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_annotation_compiles_ok/output/TestComposablegroupname.kt index 2f2de10ac..c57ccf75d 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_annotation_compiles_ok/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_annotation_compiles_ok/output/TestComposablegroupname.kt @@ -9,8 +9,7 @@ public val TestComposablegroupname: ShowkaseBrowserComponent = ShowkaseBrowserCo group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt index 2f2de10ac..c57ccf75d 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt @@ -9,8 +9,7 @@ public val TestComposablegroupname: ShowkaseBrowserComponent = ShowkaseBrowserCo group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_parameter_and_preview_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_parameter_and_preview_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt index 7a65acf6b..8abf48b99 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_parameter_and_preview_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_parameter_and_preview_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt @@ -16,8 +16,7 @@ public val TestComposablegroupname: List = group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null_$index""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null_$index""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable(text = previewParam) } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_parameter_and_showkase_composable_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_parameter_and_showkase_composable_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt index 7a65acf6b..8abf48b99 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_parameter_and_showkase_composable_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_preview_parameter_and_showkase_composable_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt @@ -16,8 +16,7 @@ public val TestComposablegroupname: List = group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null_$index""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null_$index""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable(text = previewParam) } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_generates_1_file/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_generates_1_file/output/TestComposablegroupname.kt index 2f2de10ac..c57ccf75d 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_generates_1_file/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_generates_1_file/output/TestComposablegroupname.kt @@ -9,8 +9,7 @@ public val TestComposablegroupname: ShowkaseBrowserComponent = ShowkaseBrowserCo group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_tags_and_metadata/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_tags_and_metadata/output/TestComposablegroupname.kt index f7045b168..4e629788e 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_tags_and_metadata/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_tags_and_metadata/output/TestComposablegroupname.kt @@ -9,8 +9,7 @@ public val TestComposablegroupname: ShowkaseBrowserComponent = ShowkaseBrowserCo group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", isDefaultStyle = false, tags = listOf("tag A", "tag B"), extraMetadata = listOf("meta A"), diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_tags_and_metadata/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_tags_and_metadata/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_tags_and_metadata/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_tags_and_metadata/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_width_and_height/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_width_and_height/output/TestComposablegroupname.kt index 308368b93..50b778fa8 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_width_and_height/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_width_and_height/output/TestComposablegroupname.kt @@ -9,8 +9,7 @@ public val TestComposablegroupname: ShowkaseBrowserComponent = ShowkaseBrowserCo group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", isDefaultStyle = false, widthDp = 200, heightDp = 300, diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_width_and_height/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_width_and_height/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_width_and_height/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_and_showkaseroot_with_width_and_height/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_annotation_compiles_ok/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_annotation_compiles_ok/output/TestComposablegroupname.kt index 2f2de10ac..c57ccf75d 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_annotation_compiles_ok/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_annotation_compiles_ok/output/TestComposablegroupname.kt @@ -9,8 +9,7 @@ public val TestComposablegroupname: ShowkaseBrowserComponent = ShowkaseBrowserCo group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt index 2f2de10ac..c57ccf75d 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_function_with_showkase_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt @@ -9,8 +9,7 @@ public val TestComposablegroupname: ShowkaseBrowserComponent = ShowkaseBrowserCo group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_group_name_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_with_wrapped_color_property_with_ShowkaseColor_annotation_generates_only_metadata_file/output/TestComposablecomponentname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_with_wrapped_color_property_with_ShowkaseColor_annotation_generates_only_metadata_file/output/TestComposablecomponentname.kt index 18f7eeb3b..cb5d90a99 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_with_wrapped_color_property_with_ShowkaseColor_annotation_generates_only_metadata_file/output/TestComposablecomponentname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_with_wrapped_color_property_with_ShowkaseColor_annotation_generates_only_metadata_file/output/TestComposablecomponentname.kt @@ -9,8 +9,7 @@ public val TestComposablecomponentname: ShowkaseBrowserComponent = ShowkaseBrows group = "component", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_component_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_component_name_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_with_wrapped_textstyle_property_with_ShowkaseColor_annotation_generates_only_metadata_file/output/TestComposablecomponentname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_with_wrapped_textstyle_property_with_ShowkaseColor_annotation_generates_only_metadata_file/output/TestComposablecomponentname.kt index 18f7eeb3b..cb5d90a99 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_with_wrapped_textstyle_property_with_ShowkaseColor_annotation_generates_only_metadata_file/output/TestComposablecomponentname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_composable_with_wrapped_textstyle_property_with_ShowkaseColor_annotation_generates_only_metadata_file/output/TestComposablecomponentname.kt @@ -9,8 +9,7 @@ public val TestComposablecomponentname: ShowkaseBrowserComponent = ShowkaseBrows group = "component", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_component_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_component_name_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_function_with_preview_and_preview_parameter_and_showkaseroot_and_no_name_or_group/output/TestComposable2DefaultGroupTestComposable2.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_function_with_preview_and_preview_parameter_and_showkaseroot_and_no_name_or_group/output/TestComposable2DefaultGroupTestComposable2.kt index 716a49a62..04a201743 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_function_with_preview_and_preview_parameter_and_showkaseroot_and_no_name_or_group/output/TestComposable2DefaultGroupTestComposable2.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_function_with_preview_and_preview_parameter_and_showkaseroot_and_no_name_or_group/output/TestComposable2DefaultGroupTestComposable2.kt @@ -16,8 +16,7 @@ public val TestComposable2DefaultGroupTestComposable2: List, - extraMetadata: List, - screenshotType: ShowkaseScreenshotType, - screenshotBitmap: Bitmap, - ) { - + override fun onScreenshot(metadata: ScreenshotMetadata) { + } -} \ No newline at end of file +} diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_and_class_with_@ScreenshotTest_generates_screenshot_test_for_composable/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_and_composable_function_generates_1_file/output/TestComposablecomponentname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_and_composable_function_generates_1_file/output/TestComposablecomponentname.kt index 18f7eeb3b..cb5d90a99 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_and_composable_function_generates_1_file/output/TestComposablecomponentname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_and_composable_function_generates_1_file/output/TestComposablecomponentname.kt @@ -9,8 +9,7 @@ public val TestComposablecomponentname: ShowkaseBrowserComponent = ShowkaseBrows group = "component", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing_TestComposable_null_component_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing_TestComposable_null_component_name_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { TestComposable() } diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_and_composable_function_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_and_composable_function_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_and_composable_function_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_and_composable_function_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_with_showkasetypography_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_with_showkasetypography_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_with_showkasetypography_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_with_showkasetypography_and_showkaseroot_generates_1_file/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_with_showkasetypography_and_showkaseroot_with_no_name/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_with_showkasetypography_and_showkaseroot_with_no_name/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_with_showkasetypography_and_showkaseroot_with_no_name/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/top_level_textstyle_property_with_showkasetypography_and_showkaseroot_with_no_name/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_composable_function_with_preview_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_composable_function_with_preview_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt index 36f67514a..f001789f8 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_composable_function_with_preview_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_composable_function_with_preview_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt @@ -9,8 +9,7 @@ public val TestComposablegroupname: ShowkaseBrowserComponent = ShowkaseBrowserCo group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing.WrapperClass_TestComposable_com.airbnb.android.showkase_processor_testing.WrapperClass_group_name_0_null""", + componentKey = """com.airbnb.android.showkase_processor_testing.WrapperClass_TestComposable_com.airbnb.android.showkase_processor_testing.WrapperClass_group_name_0_null""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_composable_function_with_showkase_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_composable_function_with_showkase_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt index 2af1ea8a1..1cab6d451 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_composable_function_with_showkase_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_composable_function_with_showkase_annotation_generates_only_metadata_file/output/TestComposablegroupname.kt @@ -16,8 +16,7 @@ public val TestComposablegroupname: List = group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing.WrapperClass_TestComposable_com.airbnb.android.showkase_processor_testing.WrapperClass_group_name_0_null_$index""", + componentKey = """com.airbnb.android.showkase_processor_testing.WrapperClass_TestComposable_com.airbnb.android.showkase_processor_testing.WrapperClass_group_name_0_null_$index""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_function_with_showkase_composable_and_preview_parameter_and_showkaseroot_/output/TestComposablegroupname.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_function_with_showkase_composable_and_preview_parameter_and_showkaseroot_/output/TestComposablegroupname.kt index 2af1ea8a1..1cab6d451 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_function_with_showkase_composable_and_preview_parameter_and_showkaseroot_/output/TestComposablegroupname.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_function_with_showkase_composable_and_preview_parameter_and_showkaseroot_/output/TestComposablegroupname.kt @@ -16,8 +16,7 @@ public val TestComposablegroupname: List = group = "group", componentName = "name", componentKDoc = "", - componentKey = - """com.airbnb.android.showkase_processor_testing.WrapperClass_TestComposable_com.airbnb.android.showkase_processor_testing.WrapperClass_group_name_0_null_$index""", + componentKey = """com.airbnb.android.showkase_processor_testing.WrapperClass_TestComposable_com.airbnb.android.showkase_processor_testing.WrapperClass_group_name_0_null_$index""", isDefaultStyle = false, screenshotConfig = ScreenshotConfig.SingleStaticImage, component = @Composable { diff --git a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_function_with_showkase_composable_and_preview_parameter_and_showkaseroot_/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_function_with_showkase_composable_and_preview_parameter_and_showkaseroot_/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt index f8da5d7c4..3a89e1df7 100644 --- a/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_function_with_showkase_composable_and_preview_parameter_and_showkaseroot_/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt +++ b/showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/wrapped_function_with_showkase_composable_and_preview_parameter_and_showkaseroot_/output/TestShowkaseRootShowkaseExtensionFunctionsCodegen.kt @@ -13,19 +13,20 @@ import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity */ public fun Showkase.getBrowserIntent(context: Context): Intent { val intent = Intent(context, ShowkaseBrowserActivity::class.java) - intent.putExtra("SHOWKASE_ROOT_MODULE", - "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot") + intent.putExtra( + "SHOWKASE_ROOT_MODULE", + "com.airbnb.android.showkase_processor_testing.TestShowkaseRoot" + ) return intent } /** - * Helper function that's give's you access to Showkase metadata. This contains data about the - * composables, colors and typography in your codebase that's rendered in showkase. + * Helper function that's give's you access to Showkase metadata. This contains data about the composables, colors and typography in your codebase that's rendered in showkase. */ public fun Showkase.getMetadata(): ShowkaseElementsMetadata { try { - val showkaseComponentProvider = - Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") + val showkaseComponentProvider = + Class.forName("com.airbnb.android.showkase_processor_testing.TestShowkaseRootCodegen") .getDeclaredConstructor() .newInstance() as ShowkaseProvider return showkaseComponentProvider.metadata() diff --git a/showkase-processor/build.gradle b/showkase-processor/build.gradle deleted file mode 100644 index 99caf14f9..000000000 --- a/showkase-processor/build.gradle +++ /dev/null @@ -1,46 +0,0 @@ -import com.vanniktech.maven.publish.JavaLibrary -import com.vanniktech.maven.publish.JavadocJar - -plugins { - id 'java-library' - id 'kotlin' - id 'com.vanniktech.maven.publish' -} - -kotlin { - jvmToolchain(17) -} - -// Need to apply to all tasks like this so test tasks also get these compiler args -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { - kotlinOptions { - freeCompilerArgs += [ - '-Xopt-in=kotlin.RequiresOptIn', - "-Xopt-in=kotlin.contracts.ExperimentalContracts", - "-Xopt-in=androidx.room.compiler.processing.ExperimentalProcessingApi", - "-Xopt-in=com.squareup.kotlinpoet.javapoet.KotlinPoetJavaPoetPreview", - ] - } -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - implementation project(':showkase-annotation') - - implementation deps.kotlinPoet - implementation deps.kotlinJavaPoetInterop - implementation deps.kotlinXMetadata - implementation deps.ksp - implementation deps.xprocessing - - testImplementation deps.test.strikt - testImplementation deps.test.junit - testImplementation deps.xprocessingTesting -} - -mavenPublishing { - configure(new JavaLibrary(new JavadocJar.Javadoc(), true)) -} diff --git a/showkase-processor/build.gradle.kts b/showkase-processor/build.gradle.kts new file mode 100644 index 000000000..01cd38541 --- /dev/null +++ b/showkase-processor/build.gradle.kts @@ -0,0 +1,36 @@ +import com.vanniktech.maven.publish.JavaLibrary +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.SourcesJar + +plugins { + `java-library` + id("org.jetbrains.kotlin.jvm") + id("com.vanniktech.maven.publish") +} + +kotlin { + jvmToolchain(21) + compilerOptions { + freeCompilerArgs.addAll( + "-opt-in=kotlin.RequiresOptIn", + "-opt-in=kotlin.contracts.ExperimentalContracts", + ) + } +} + +dependencies { + implementation(project(":showkase-annotation")) + + implementation(libs.kotlinPoet) + implementation(libs.kotlinPoetKsp) + implementation(libs.ksp) + + testImplementation(libs.test.strikt) + testImplementation(libs.test.junit) + testImplementation(libs.kotlinCompileTesting) + testImplementation(libs.kotlinCompileTestingKsp) +} + +mavenPublishing { + configure(JavaLibrary(JavadocJar.Javadoc(), SourcesJar.Sources())) +} diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/BaseProcessor.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/BaseProcessor.kt index f0807e668..47a258280 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/BaseProcessor.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/BaseProcessor.kt @@ -1,102 +1,44 @@ package com.airbnb.android.showkase.processor -import androidx.room.compiler.processing.XFiler -import androidx.room.compiler.processing.XMessager -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.XRoundEnv import com.airbnb.android.showkase.processor.exceptions.ShowkaseProcessorException +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.symbol.KSAnnotated -import javax.annotation.processing.AbstractProcessor -import javax.annotation.processing.ProcessingEnvironment -import javax.annotation.processing.RoundEnvironment -import javax.lang.model.SourceVersion -import javax.lang.model.element.TypeElement -import javax.tools.Diagnostic -/** - * Creates a unified abstraction for processors of both KSP and java annotation processing. - */ abstract class BaseProcessor( - val kspEnvironment: SymbolProcessorEnvironment? = null -) : AbstractProcessor(), SymbolProcessor { + val kspEnvironment: SymbolProcessorEnvironment, +) : SymbolProcessor { - lateinit var environment: XProcessingEnv - private set - - val messager: XMessager - get() = environment.messager - - val filer: XFiler - get() = environment.filer + val codeGenerator: CodeGenerator = kspEnvironment.codeGenerator + val logger: KSPLogger = kspEnvironment.logger + val options: Map = kspEnvironment.options private var roundNumber = 1 init { - if (kspEnvironment != null) { - initOptions(kspEnvironment.options) - } - } - - fun isKsp(): Boolean = kspEnvironment != null - - override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.RELEASE_17 - - override fun init(processingEnv: ProcessingEnvironment) { - super.init(processingEnv) - environment = XProcessingEnv.create(processingEnv) - initOptions(processingEnv.options) + initOptions(options) } /** - * Unified place to handle any compiler processor options that are passed to - * either javac processor or KSP processor, + * Unified place to handle any compiler processor options that are passed to the KSP processor, * before any rounds are processed. */ open fun initOptions(options: Map) {} - final override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { - if (roundEnv.errorRaised()) { - onError() - } - - internalProcess(environment, XRoundEnv.create(environment, roundEnv)) - - if (roundEnv.processingOver()) { - finish() - } - - return false - } - final override fun process(resolver: Resolver): List { - val kspEnvironment = requireNotNull(kspEnvironment) - environment = XProcessingEnv.create( - kspEnvironment, - resolver, - ) - internalProcess(environment, XRoundEnv.create(environment)) - return emptyList() - } - - private fun internalProcess( - environment: XProcessingEnv, - round: XRoundEnv - ) { - val timer = - Timer( - this.javaClass.simpleName + " [Round $roundNumber][${if (isKsp()) "ksp" else "javac"}]" - ) + val timer = Timer("${this.javaClass.simpleName} [Round $roundNumber]") timer.start() tryOrPrintError { - process(environment, round) + processRound(resolver) } - timer.finishAndPrint(messager) + timer.finishAndPrint(logger) roundNumber++ + return emptyList() } private inline fun tryOrPrintError(block: () -> Unit) { @@ -107,15 +49,12 @@ abstract class BaseProcessor( // Errors thrown from within KSP can get lost, making the root cause of an issue hidden. // This helps to surface all thrown errors. if (e is ShowkaseProcessorException && e.element != null) { - messager.printMessage(Diagnostic.Kind.ERROR, e.stackTraceToString(), e.element) + logger.error(e.stackTraceToString(), e.element) } else { - messager.printMessage(Diagnostic.Kind.ERROR, e.stackTraceToString()) + logger.error(e.stackTraceToString()) } } } - abstract fun process( - environment: XProcessingEnv, - round: XRoundEnv - ) + abstract fun processRound(resolver: Resolver) } diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/ShowkaseProcessor.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/ShowkaseProcessor.kt index 7b36b942a..2f77f39ea 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/ShowkaseProcessor.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/ShowkaseProcessor.kt @@ -1,13 +1,9 @@ package com.airbnb.android.showkase.processor -import androidx.room.compiler.processing.XAnnotation -import androidx.room.compiler.processing.XElement -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.XRoundEnv -import androidx.room.compiler.processing.XTypeElement import com.airbnb.android.showkase.annotation.ShowkaseCodegenMetadata import com.airbnb.android.showkase.annotation.ShowkaseColor import com.airbnb.android.showkase.annotation.ShowkaseComposable +import com.airbnb.android.showkase.annotation.ShowkaseDialog import com.airbnb.android.showkase.annotation.ShowkaseMultiPreviewCodegenMetadata import com.airbnb.android.showkase.annotation.ShowkaseRoot import com.airbnb.android.showkase.annotation.ShowkaseRootCodegen @@ -20,12 +16,20 @@ import com.airbnb.android.showkase.processor.models.ShowkaseMetadata import com.airbnb.android.showkase.processor.models.ShowkaseMetadataType import com.airbnb.android.showkase.processor.models.getCodegenMetadataTypes import com.airbnb.android.showkase.processor.models.getShowkaseColorMetadata +import com.airbnb.android.showkase.processor.models.getShowkaseDialogMetadata import com.airbnb.android.showkase.processor.models.getShowkaseMetadata import com.airbnb.android.showkase.processor.models.getShowkaseMetadataFromCustomAnnotation import com.airbnb.android.showkase.processor.models.getShowkaseMetadataFromPreview import com.airbnb.android.showkase.processor.models.getShowkaseTypographyMetadata import com.airbnb.android.showkase.processor.utils.ensureConsistentOrdering -import com.airbnb.android.showkase.processor.writer.PaparazziShowkaseScreenshotTestWriter +import com.airbnb.android.showkase.processor.utils.findAnnotationBySimpleName +import com.airbnb.android.showkase.processor.utils.getAnnotation +import com.airbnb.android.showkase.processor.utils.getAsBoolean +import com.airbnb.android.showkase.processor.utils.getAsInt +import com.airbnb.android.showkase.processor.utils.getAsString +import com.airbnb.android.showkase.processor.utils.getAsStringList +import com.airbnb.android.showkase.processor.utils.requireAnnotation +import com.airbnb.android.showkase.processor.writer.RoborazziShowkaseScreenshotTestWriter import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserProperties import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserPropertyWriter import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter @@ -33,11 +37,16 @@ import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Compan import com.airbnb.android.showkase.processor.writer.ShowkaseCodegenMetadataWriter import com.airbnb.android.showkase.processor.writer.ShowkaseExtensionFunctionsWriter import com.airbnb.android.showkase.processor.writer.ShowkaseScreenshotTestWriter +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.processing.SymbolProcessorProvider -import javax.annotation.processing.SupportedSourceVersion -import javax.lang.model.SourceVersion +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSPropertyDeclaration class ShowkaseProcessorProvider : SymbolProcessorProvider { override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { @@ -45,53 +54,21 @@ class ShowkaseProcessorProvider : SymbolProcessorProvider { } } -@SupportedSourceVersion(SourceVersion.RELEASE_17) -class ShowkaseProcessor @JvmOverloads constructor( - kspEnvironment: SymbolProcessorEnvironment? = null +class ShowkaseProcessor( + kspEnvironment: SymbolProcessorEnvironment, ) : BaseProcessor(kspEnvironment) { - private val logger = ShowkaseExceptionLogger() - private val showkaseValidator by lazy { ShowkaseValidator(environment) } - - override fun getSupportedAnnotationTypes(): MutableSet { - val supportedAnnotations = mutableSetOf( - ShowkaseComposable::class.java.name, - PREVIEW_CLASS_NAME, - ShowkaseColor::class.java.name, - ShowkaseTypography::class.java.name, - ShowkaseRoot::class.java.name, - ShowkaseScreenshot::class.java.name, - ShowkaseMultiPreviewCodegenMetadata::class.java.name, - ) - supportedAnnotations.addAll(supportedCustomAnnotationTypes()) - return supportedAnnotations - } - - // Getting the custom annotations that are supported as an compiler argument. - // It is expected to get the compiler argument as follows: - // arg("multiPreviewTypes", "com.airbnb.android.submodule.showkasesample.FontPreview") - // This should only be provided by KAPT users - private fun supportedCustomAnnotationTypes(): MutableSet { - val set = mutableSetOf() - environment - .options["multiPreviewType"] - ?.split(",")?.map { it.replace(" ", "") } - ?.toSet()?.let { set.addAll(it) } - return set - } - - override fun getSupportedOptions() = mutableSetOf( - "skipPrivatePreviews", - "requireShowkaseComposableAnnotation", - "multiPreviewType" - ) + private val exceptionLogger = ShowkaseExceptionLogger() + private lateinit var currentResolver: Resolver + private val showkaseValidator by lazy { ShowkaseValidator { currentResolver } } - override fun process(environment: XProcessingEnv, round: XRoundEnv) { - val componentMetadata = processComponentAnnotation(round) - val colorMetadata = processColorAnnotation(round) - val typographyMetadata = processTypographyAnnotation(round, environment) + override fun processRound(resolver: Resolver) { + currentResolver = resolver + val componentMetadata = processComponentAnnotation(resolver) + val colorMetadata = processColorAnnotation(resolver) + val typographyMetadata = processTypographyAnnotation(resolver) processShowkaseMetadata( - roundEnvironment = round, + resolver = resolver, componentMetadata = componentMetadata, colorMetadata = colorMetadata, typographyMetadata = typographyMetadata @@ -99,24 +76,27 @@ class ShowkaseProcessor @JvmOverloads constructor( } override fun finish() { - logger.publishMessages(messager) + exceptionLogger.publishMessages(logger) } - private fun processComponentAnnotation(roundEnvironment: XRoundEnv): Set { - val showkaseComposablesMetadata = processShowkaseAnnotation(roundEnvironment) - val previewComposablesMetadata = processPreviewAnnotation(roundEnvironment) + private fun processComponentAnnotation(resolver: Resolver): Set { + val showkaseComposablesMetadata = processShowkaseAnnotation(resolver) + val previewComposablesMetadata = processPreviewAnnotation(resolver) - val customPreviewFromClassPathMetadata = processCustomAnnotationFromClasspath(roundEnvironment) - return (showkaseComposablesMetadata + previewComposablesMetadata + customPreviewFromClassPathMetadata) + val customPreviewFromClassPathMetadata = processCustomAnnotationFromClasspath(resolver) + val showkaseDialogsMetadata = processShowkaseDialogAnnotation(resolver) + return (showkaseComposablesMetadata + previewComposablesMetadata + + customPreviewFromClassPathMetadata + showkaseDialogsMetadata) .dedupeAndSort() .toSet() } private fun processShowkaseAnnotation( - roundEnvironment: XRoundEnv + resolver: Resolver ): Set { - val skipPrivatePreviews = environment.options["skipPrivatePreviews"].toBoolean() - return roundEnvironment.getElementsAnnotatedWith(ShowkaseComposable::class) + val skipPrivatePreviews = options["skipPrivatePreviews"].toBoolean() + return resolver.getSymbolsWithAnnotation(ShowkaseComposable::class.qualifiedName!!) + .toList() .ensureConsistentOrdering() .mapNotNull { element -> if (showkaseValidator.checkElementIsAnnotationClass(element)) return@mapNotNull null @@ -127,31 +107,54 @@ class ShowkaseProcessor @JvmOverloads constructor( ) if (skipElement) return@mapNotNull null getShowkaseMetadata( - element = element, + element = element as KSFunctionDeclaration, + showkaseValidator = showkaseValidator, + ) + }.flatten().mapNotNull { it }.toSet() + } + + private fun processShowkaseDialogAnnotation( + resolver: Resolver + ): Set { + val skipPrivatePreviews = options["skipPrivatePreviews"].toBoolean() + return resolver.getSymbolsWithAnnotation(ShowkaseDialog::class.qualifiedName!!) + .toList() + .ensureConsistentOrdering() + .mapNotNull { element -> + if (showkaseValidator.checkElementIsAnnotationClass(element)) return@mapNotNull null + val skipElement = showkaseValidator.validateComponentElementOrSkip( + element, + ShowkaseDialog::class.java.simpleName, + skipPrivatePreviews + ) + if (skipElement) return@mapNotNull null + getShowkaseDialogMetadata( + element = element as KSFunctionDeclaration, showkaseValidator = showkaseValidator, ) }.flatten().mapNotNull { it }.toSet() } - private fun processPreviewAnnotation(roundEnvironment: XRoundEnv): Set { - val skipPrivatePreviews = environment.options["skipPrivatePreviews"].toBoolean() + private fun processPreviewAnnotation(resolver: Resolver): Set { + val skipPrivatePreviews = options["skipPrivatePreviews"].toBoolean() val requireShowkaseComposableAnnotation = - environment.options["requireShowkaseComposableAnnotation"].toBoolean() + options["requireShowkaseComposableAnnotation"].toBoolean() if (requireShowkaseComposableAnnotation) return emptySet() - return roundEnvironment.getElementsAnnotatedWith(PREVIEW_CLASS_NAME) + return resolver.getSymbolsWithAnnotation(PREVIEW_CLASS_NAME) + .toList() .ensureConsistentOrdering() .mapNotNull { element -> if (showkaseValidator.checkElementIsAnnotationClass(element)) { // Writing preview data to a internal annotation to store values through // processing rounds - ShowkaseBrowserWriter(environment).writeCustomAnnotationElementToMetadata( + ShowkaseBrowserWriter(codeGenerator).writeCustomAnnotationElementToMetadata( element ) return@mapNotNull processCustomAnnotation( skipPrivatePreviews = skipPrivatePreviews, - roundEnvironment = roundEnvironment, + resolver = resolver, annotation = element ) } @@ -162,7 +165,7 @@ class ShowkaseProcessor @JvmOverloads constructor( ) if (skipElement) return@mapNotNull null getShowkaseMetadataFromPreview( - element = element, + element = element as KSFunctionDeclaration, showkaseValidator = showkaseValidator ) } @@ -171,15 +174,17 @@ class ShowkaseProcessor @JvmOverloads constructor( private fun processCustomAnnotation( skipPrivatePreviews: Boolean, - roundEnvironment: XRoundEnv, - annotation: XTypeElement? = null + resolver: Resolver, + annotation: KSClassDeclaration? = null ): Set { val supportedTypes = mutableListOf() - if (annotation != null) supportedTypes.add(annotation.qualifiedName) + if (annotation != null) { + annotation.qualifiedName?.asString()?.let { supportedTypes.add(it) } + } val components = mutableSetOf() supportedTypes.map { supportedType -> - val annotatedElements = roundEnvironment.getElementsAnnotatedWith(supportedType) + val annotatedElements = resolver.getSymbolsWithAnnotation(supportedType).toList() annotatedElements .map { annotatedElement -> if (!showkaseValidator.checkElementIsAnnotationClass(annotatedElement)) { @@ -191,7 +196,7 @@ class ShowkaseProcessor @JvmOverloads constructor( if (!skipable) { components.addAll( getShowkaseMetadataFromCustomAnnotation( - element = annotatedElement, + element = annotatedElement as KSFunctionDeclaration, showkaseValidator = showkaseValidator, supportedType.getCustomAnnotationSimpleName(), ).toSet() @@ -207,18 +212,22 @@ class ShowkaseProcessor @JvmOverloads constructor( return this.split(".").last() } - private fun processCustomAnnotationFromClasspath(roundEnvironment: XRoundEnv): Set { + @OptIn(KspExperimental::class) + private fun processCustomAnnotationFromClasspath(resolver: Resolver): Set { // In this function we are checking generated classpath for MultiPreview codegen annotations. // We also check the current module if there is any composables that are annotated with the qualified name // from the annotation from classpath. We use the fields from the classpath annotation to build // common data for the ShowkaseMetadata. - val skipPrivatePreviews = environment.options["skipPrivatePreviews"] == "true" + val skipPrivatePreviews = options["skipPrivatePreviews"] == "true" // Supported annotations from classpath val supportedCustomPreview = mutableSetOf() - environment.getTypeElementsFromPackage(CODEGEN_PACKAGE_NAME) + resolver.getDeclarationsFromPackage(CODEGEN_PACKAGE_NAME) + .filterIsInstance() + .toList() .ensureConsistentOrdering() - .flatMap { it.getEnclosedElements() }.mapNotNull { + .flatMap { it.declarations.toList() } + .mapNotNull { return@mapNotNull when ( val annotation = it.getAnnotation(ShowkaseMultiPreviewCodegenMetadata::class) ) { @@ -235,7 +244,6 @@ class ShowkaseProcessor @JvmOverloads constructor( showkaseWidth = annotation.getAsInt("showkaseWidth"), showkaseHeight = annotation.getAsInt("showkaseHeight"), ) - supportedAnnotationTypes.add(annotation.getAsString("supportTypeQualifiedName")) supportedCustomPreview.add(codeGenAnnotation) } } @@ -243,8 +251,9 @@ class ShowkaseProcessor @JvmOverloads constructor( val components = mutableSetOf() supportedCustomPreview .mapIndexed { index: Int, customPreviewMetadata: ShowkaseMultiPreviewCodegenMetadata -> - roundEnvironment - .getElementsAnnotatedWith(customPreviewMetadata.supportTypeQualifiedName) + resolver + .getSymbolsWithAnnotation(customPreviewMetadata.supportTypeQualifiedName) + .toList() .ensureConsistentOrdering() .mapIndexed elementRoot@{ elementIndex, xElement -> val skippable = showkaseValidator.validateComponentElementOrSkip( @@ -255,7 +264,7 @@ class ShowkaseProcessor @JvmOverloads constructor( if (!skippable) { components.add( getShowkaseMetadata( - xElement = xElement, + xElement = xElement as KSFunctionDeclaration, customPreviewMetadata = customPreviewMetadata, elementIndex = elementIndex, index = index, @@ -276,10 +285,10 @@ class ShowkaseProcessor @JvmOverloads constructor( val aggregateMetadataList = componentMetadata + colorMetadata + typographyMetadata if (aggregateMetadataList.isEmpty()) return ShowkaseBrowserProperties() - ShowkaseCodegenMetadataWriter(environment).apply { + ShowkaseCodegenMetadataWriter(codeGenerator).apply { generateShowkaseCodegenFunctions(aggregateMetadataList) } - ShowkaseBrowserPropertyWriter(environment).apply { + ShowkaseBrowserPropertyWriter(codeGenerator).apply { return generateMetadataPropertyFiles( componentMetadata = componentMetadata, colorMetadata = colorMetadata, @@ -318,27 +327,30 @@ class ShowkaseProcessor @JvmOverloads constructor( it.fqPrefix } - private fun processColorAnnotation(roundEnvironment: XRoundEnv): Set { - return roundEnvironment.getElementsAnnotatedWith(ShowkaseColor::class) + private fun processColorAnnotation(resolver: Resolver): Set { + return resolver.getSymbolsWithAnnotation(ShowkaseColor::class.qualifiedName!!) + .toList() .ensureConsistentOrdering() .map { element -> showkaseValidator.validateColorElement( element, ShowkaseColor::class.java.simpleName ) - getShowkaseColorMetadata(element, showkaseValidator) + getShowkaseColorMetadata(element as KSPropertyDeclaration, showkaseValidator) }.toSet() } private fun processTypographyAnnotation( - roundEnvironment: XRoundEnv, - environment: XProcessingEnv + resolver: Resolver, ): Set { val textStyleType by lazy { - environment.requireType(TYPE_STYLE_CLASS_NAME) + resolver.getClassDeclarationByName(resolver.getKSNameFromString(TYPE_STYLE_CLASS_NAME)) + ?.asStarProjectedType() + ?: error("TextStyle type not found") } - return roundEnvironment.getElementsAnnotatedWith(ShowkaseTypography::class) + return resolver.getSymbolsWithAnnotation(ShowkaseTypography::class.qualifiedName!!) + .toList() .ensureConsistentOrdering() .map { element -> showkaseValidator.validateTypographyElement( @@ -346,22 +358,22 @@ class ShowkaseProcessor @JvmOverloads constructor( ShowkaseTypography::class.java.simpleName, textStyleType ) - getShowkaseTypographyMetadata(element, showkaseValidator) + getShowkaseTypographyMetadata(element as KSPropertyDeclaration, showkaseValidator) }.toSet() } private fun processShowkaseMetadata( - roundEnvironment: XRoundEnv, + resolver: Resolver, componentMetadata: Set, colorMetadata: Set, typographyMetadata: Set ) { // Showkase root annotation - val rootElement = getShowkaseRootElement(roundEnvironment, environment) + val rootElement = getShowkaseRootElement(resolver) // Showkase test annotation val (screenshotTestElement, screenshotTestType) = getShowkaseScreenshotTestElement( - roundEnvironment + resolver ) var showkaseBrowserProperties = ShowkaseBrowserProperties() @@ -391,33 +403,34 @@ class ShowkaseProcessor @JvmOverloads constructor( } private fun getShowkaseRootElement( - roundEnvironment: XRoundEnv, - environment: XProcessingEnv - ): XTypeElement? { - val showkaseRootElements = roundEnvironment.getElementsAnnotatedWith(ShowkaseRoot::class) + resolver: Resolver, + ): KSClassDeclaration? { + val showkaseRootElements = resolver.getSymbolsWithAnnotation(ShowkaseRoot::class.qualifiedName!!) + .toList() .ensureConsistentOrdering().toSet() - showkaseValidator.validateShowkaseRootElement(showkaseRootElements, environment) - return showkaseRootElements.singleOrNull() as XTypeElement? + showkaseValidator.validateShowkaseRootElement(showkaseRootElements) + return showkaseRootElements.singleOrNull() as KSClassDeclaration? } private fun getShowkaseScreenshotTestElement( - roundEnvironment: XRoundEnv - ): Pair { - val testElements = roundEnvironment.getElementsAnnotatedWith(ShowkaseScreenshot::class) + resolver: Resolver + ): Pair { + val testElements = resolver.getSymbolsWithAnnotation(ShowkaseScreenshot::class.qualifiedName!!) + .toList() .ensureConsistentOrdering() - .filterIsInstance() + .filterIsInstance() .toSet() val screenshotTestType = - showkaseValidator.validateShowkaseTestElement(testElements, environment) + showkaseValidator.validateShowkaseTestElement(testElements) return testElements.singleOrNull() to screenshotTestType } private fun writeShowkaseFiles( - rootElement: XTypeElement, + rootElement: KSClassDeclaration, currentShowkaseBrowserProperties: ShowkaseBrowserProperties, ): ShowkaseBrowserProperties { val generatedShowkaseMetadataOnClasspath = - getShowkaseCodegenMetadataOnClassPath(environment) + getShowkaseCodegenMetadataOnClassPath(currentResolver) val classpathComponentsWithoutParameter = generatedShowkaseMetadataOnClasspath.filter { it.type == ShowkaseGeneratedMetadataType.COMPONENTS_WITHOUT_PARAMETER } @@ -447,23 +460,23 @@ class ShowkaseProcessor @JvmOverloads constructor( } private fun writeScreenshotTestFiles( - screenshotTestElement: XTypeElement, + screenshotTestElement: KSClassDeclaration, screenshotTestType: ScreenshotTestType, - rootElement: XTypeElement?, + rootElement: KSClassDeclaration?, showkaseBrowserProperties: ShowkaseBrowserProperties, ) { - val testClassName = screenshotTestElement.name - val screenshotTestPackageName = screenshotTestElement.packageName + val testClassName = screenshotTestElement.simpleName.asString() + val screenshotTestPackageName = screenshotTestElement.packageName.asString() // Parse the showkase root class that was specified in @ShowkaseScreenshot val specifiedRootClassTypeElement = getSpecifiedRootTypeElement(screenshotTestElement) // Get the package of the specified root module. We need this to ensure that we use the // Showkase.getMetadata metadata from that package. - val rootModulePackageName = specifiedRootClassTypeElement.packageName + val rootModulePackageName = specifiedRootClassTypeElement.packageName.asString() val showkaseTestMetadata = if (rootElement != null && - specifiedRootClassTypeElement.name == rootElement.name + specifiedRootClassTypeElement.simpleName.asString() == rootElement.simpleName.asString() ) { // If the specified root element is the being processed in the current processing round, // use it directly instead of looking for it in the class path. This is because it won't @@ -500,21 +513,28 @@ class ShowkaseProcessor @JvmOverloads constructor( ) } - private fun getSpecifiedRootTypeElement(screenshotTestElement: XTypeElement): XTypeElement { - return screenshotTestElement.requireAnnotation(ShowkaseScreenshot::class) - .getAsType("rootShowkaseClass") - .typeElement + private fun getSpecifiedRootTypeElement(screenshotTestElement: KSClassDeclaration): KSClassDeclaration { + val rootShowkaseClassType = screenshotTestElement.requireAnnotation(ShowkaseScreenshot::class) + .let { ann -> + val arg = ann.arguments.firstOrNull { it.name?.asString() == "rootShowkaseClass" } + ?: ann.defaultArguments.firstOrNull { it.name?.asString() == "rootShowkaseClass" } + arg?.value as? com.google.devtools.ksp.symbol.KSType + } + return (rootShowkaseClassType?.declaration as? KSClassDeclaration) ?: throw ShowkaseProcessorException( "Unable to get rootShowkaseClass in ShowkaseScreenshot annotation", screenshotTestElement ) } - private fun getShowkaseCodegenMetadataOnClassPath(environment: XProcessingEnv): + @OptIn(KspExperimental::class) + private fun getShowkaseCodegenMetadataOnClassPath(resolver: Resolver): Set { - return environment.getTypeElementsFromPackage(CODEGEN_PACKAGE_NAME) + return resolver.getDeclarationsFromPackage(CODEGEN_PACKAGE_NAME) + .filterIsInstance() + .toList() .ensureConsistentOrdering() - .flatMap { it.getEnclosedElements() } + .flatMap { it.declarations.toList() } .mapNotNull { element -> val codegenMetadataAnnotation = element.getAnnotation(ShowkaseCodegenMetadata::class) @@ -529,7 +549,7 @@ class ShowkaseProcessor @JvmOverloads constructor( .toSet() } - private fun XAnnotation.toShowkaseGeneratedMetadata(element: XElement): ShowkaseGeneratedMetadata { + private fun KSAnnotation.toShowkaseGeneratedMetadata(element: KSAnnotated): ShowkaseGeneratedMetadata { val (_, previewParameterClassType) = getCodegenMetadataTypes() // The box is needed to get all Class values, primitives can be accessed dirctly @@ -552,16 +572,21 @@ class ShowkaseProcessor @JvmOverloads constructor( name = getAsString("showkaseName"), isDefaultStyle = getAsBoolean("isDefaultStyle"), tags = getAsStringList("tags"), - extraMetadata = getAsStringList("extraMetadata") + extraMetadata = getAsStringList("extraMetadata"), + isDialog = getAsBoolean("isDialog"), + dialogButtonText = getAsString("dialogButtonText"), + dialogHideButtonText = getAsString("dialogHideButtonText"), ) } private fun getShowkaseRootCodegenOnClassPath( - specifiedRootClassTypeElement: XTypeElement + specifiedRootClassTypeElement: KSClassDeclaration ): ShowkaseRootCodegen? { - return environment - .findTypeElement("${specifiedRootClassTypeElement.qualifiedName}$CODEGEN_AUTOGEN_CLASS_NAME") - ?.getAnnotation(ShowkaseRootCodegen::class)?.let { xAnnotation -> + val qName = "${specifiedRootClassTypeElement.qualifiedName?.asString()}$CODEGEN_AUTOGEN_CLASS_NAME" + return currentResolver + .getClassDeclarationByName(currentResolver.getKSNameFromString(qName)) + ?.findAnnotationBySimpleName(ShowkaseRootCodegen::class.simpleName!!) + ?.let { xAnnotation -> ShowkaseRootCodegen( numComposablesWithoutPreviewParameter = xAnnotation.getAsInt("numComposablesWithoutPreviewParameter"), @@ -573,16 +598,16 @@ class ShowkaseProcessor @JvmOverloads constructor( } private fun writeShowkaseBrowserFiles( - rootElement: XTypeElement, + rootElement: KSClassDeclaration, allShowkaseBrowserProperties: ShowkaseBrowserProperties, ) { if (allShowkaseBrowserProperties.isEmpty()) return - val rootModuleClassName = rootElement.name - val rootModulePackageName = rootElement.packageName + val rootModuleClassName = rootElement.simpleName.asString() + val rootModulePackageName = rootElement.packageName.asString() showkaseValidator.validateShowkaseComponents(allShowkaseBrowserProperties) - ShowkaseBrowserWriter(environment).apply { + ShowkaseBrowserWriter(codeGenerator).apply { generateShowkaseBrowserFile( allShowkaseBrowserProperties, rootModulePackageName, @@ -590,7 +615,7 @@ class ShowkaseProcessor @JvmOverloads constructor( ) } - ShowkaseExtensionFunctionsWriter(environment).apply { + ShowkaseExtensionFunctionsWriter(codeGenerator).apply { generateShowkaseExtensionFunctions( rootModulePackageName = rootModulePackageName, rootModuleClassName = rootModuleClassName, @@ -617,7 +642,7 @@ class ShowkaseProcessor @JvmOverloads constructor( // TODO(vinaygaba): Add screenshot testing support for composabable with preview // parameters as well ScreenshotTestType.SHOWKASE -> { - ShowkaseScreenshotTestWriter(environment).apply { + ShowkaseScreenshotTestWriter(codeGenerator).apply { generateScreenshotTests( componentsSize, colorsSize, @@ -629,8 +654,8 @@ class ShowkaseProcessor @JvmOverloads constructor( } } - ScreenshotTestType.PAPARAZZI_SHOWKASE -> { - PaparazziShowkaseScreenshotTestWriter(environment).apply { + ScreenshotTestType.ROBORAZZI_SHOWKASE -> { + RoborazziShowkaseScreenshotTestWriter(codeGenerator).apply { generateScreenshotTests( screenshotTestPackageName, rootModulePackageName, @@ -661,13 +686,16 @@ internal data class ShowkaseGeneratedMetadata( val propertyName: String, val propertyPackage: String, val type: ShowkaseGeneratedMetadataType, - val element: XElement, + val element: KSAnnotated, val group: String, val name: String, // This property is only used for components val isDefaultStyle: Boolean = false, val tags: List = emptyList(), - val extraMetadata: List = emptyList() + val extraMetadata: List = emptyList(), + val isDialog: Boolean = false, + val dialogButtonText: String = "", + val dialogHideButtonText: String = "", ) internal enum class ShowkaseGeneratedMetadataType { @@ -679,5 +707,5 @@ internal enum class ShowkaseGeneratedMetadataType { internal enum class ScreenshotTestType { SHOWKASE, - PAPARAZZI_SHOWKASE + ROBORAZZI_SHOWKASE, } diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/Timer.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/Timer.kt index b65cb418a..6a848890a 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/Timer.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/Timer.kt @@ -1,7 +1,6 @@ package com.airbnb.android.showkase.processor -import androidx.room.compiler.processing.XMessager -import javax.tools.Diagnostic +import com.google.devtools.ksp.processing.KSPLogger import kotlin.math.pow import kotlin.math.roundToInt @@ -24,7 +23,7 @@ class Timer(val name: String) { timingSteps.add(TimingStep(nowNanos - lastNanos, stepDescription)) } - fun finishAndPrint(messager: XMessager) { + fun finishAndPrint(logger: KSPLogger) { val start = startNanos ?: error("Timer was not started") val message = buildString { appendLine("$name finished in ${formatNanos(System.nanoTime() - start)}") @@ -33,7 +32,7 @@ class Timer(val name: String) { } } - messager.printMessage(Diagnostic.Kind.OTHER, message) + logger.info(message) } private class TimingStep(val durationNanos: Long, val description: String) diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/exceptions/ShowkaseProcessorException.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/exceptions/ShowkaseProcessorException.kt index d5a860c5c..97a66e309 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/exceptions/ShowkaseProcessorException.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/exceptions/ShowkaseProcessorException.kt @@ -1,5 +1,5 @@ package com.airbnb.android.showkase.processor.exceptions -import androidx.room.compiler.processing.XElement +import com.google.devtools.ksp.symbol.KSNode -internal class ShowkaseProcessorException(message: String, val element: XElement? = null) : Exception(message) +internal class ShowkaseProcessorException(message: String, val element: KSNode? = null) : Exception(message) diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/logging/ShowkaseExceptionLogger.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/logging/ShowkaseExceptionLogger.kt index 7388d204c..5ae50ed14 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/logging/ShowkaseExceptionLogger.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/logging/ShowkaseExceptionLogger.kt @@ -1,8 +1,7 @@ package com.airbnb.android.showkase.processor.logging -import androidx.room.compiler.processing.XMessager import com.airbnb.android.showkase.processor.exceptions.ShowkaseProcessorException -import javax.tools.Diagnostic +import com.google.devtools.ksp.processing.KSPLogger internal class ShowkaseExceptionLogger { private val loggedExceptions: MutableList = mutableListOf() @@ -20,14 +19,14 @@ internal class ShowkaseExceptionLogger { loggedExceptions += e } - internal fun publishMessages(messager: XMessager) { + internal fun publishMessages(logger: KSPLogger) { loggedExceptions.forEach { if (it is ShowkaseProcessorException && it.element != null) { - messager.printMessage(Diagnostic.Kind.ERROR, "${it.message}", it.element) + logger.error("${it.message}", it.element) } else { - messager.printMessage(Diagnostic.Kind.ERROR, "${it.message}") + logger.error("${it.message}") } } - loggedInfoMessage.forEach { messager.printMessage(Diagnostic.Kind.NOTE, it) } + loggedInfoMessage.forEach { logger.info(it) } } } diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/logging/ShowkaseValidator.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/logging/ShowkaseValidator.kt index d41bb37d4..ae33b4ace 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/logging/ShowkaseValidator.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/logging/ShowkaseValidator.kt @@ -1,16 +1,5 @@ package com.airbnb.android.showkase.processor.logging -import androidx.room.compiler.processing.XElement -import androidx.room.compiler.processing.XFieldElement -import androidx.room.compiler.processing.XMethodElement -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.XType -import androidx.room.compiler.processing.XTypeElement -import androidx.room.compiler.processing.compat.XConverters.toJavac -import androidx.room.compiler.processing.isField -import androidx.room.compiler.processing.isLong -import androidx.room.compiler.processing.isMethod -import androidx.room.compiler.processing.isTypeElement import com.airbnb.android.showkase.annotation.ShowkaseComposable import com.airbnb.android.showkase.annotation.ShowkaseRoot import com.airbnb.android.showkase.annotation.ShowkaseRootModule @@ -19,31 +8,47 @@ import com.airbnb.android.showkase.processor.ScreenshotTestType import com.airbnb.android.showkase.processor.ShowkaseProcessor.Companion.COMPOSABLE_SIMPLE_NAME import com.airbnb.android.showkase.processor.ShowkaseProcessor.Companion.PREVIEW_PARAMETER_SIMPLE_NAME import com.airbnb.android.showkase.processor.exceptions.ShowkaseProcessorException -import com.airbnb.android.showkase.processor.models.isJavac import com.airbnb.android.showkase.processor.utils.findAnnotationBySimpleName -import com.airbnb.android.showkase.processor.utils.kotlinMetadata +import com.airbnb.android.showkase.processor.utils.isSameTypeAs import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserProperties -import javax.lang.model.element.Element +import com.google.devtools.ksp.getConstructors +import com.google.devtools.ksp.isOpen +import com.google.devtools.ksp.isPrivate +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.symbol.ClassKind +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.google.devtools.ksp.symbol.KSType import kotlin.contracts.contract -import kotlin.metadata.KmFunction -import kotlin.metadata.declaresDefaultValue -import kotlin.metadata.jvm.KotlinClassMetadata -internal class ShowkaseValidator(private val environment: XProcessingEnv) { +internal class ShowkaseValidator(private val resolverProvider: () -> Resolver) { - private val colorType by lazy { environment.requireType("androidx.compose.ui.graphics.Color") } + private val resolver: Resolver get() = resolverProvider() + + private fun requireType(qualifiedName: String): KSType = + resolver.getClassDeclarationByName(resolver.getKSNameFromString(qualifiedName)) + ?.asStarProjectedType() + ?: error("Type not found: $qualifiedName") + + private fun findType(qualifiedName: String): KSType? = + resolver.getClassDeclarationByName(resolver.getKSNameFromString(qualifiedName)) + ?.asStarProjectedType() + + private val colorType by lazy { requireType("androidx.compose.ui.graphics.Color") } @Suppress("ThrowsCount") internal fun validateComponentElementOrSkip( - element: XElement, + element: KSAnnotated, annotationName: String, skipPrivatePreviews: Boolean = false ): Boolean { contract { - returns() implies (element is XMethodElement) + returns() implies (element is KSFunctionDeclaration) } when { - !element.isMethod() -> { + element !is KSFunctionDeclaration -> { throw ShowkaseProcessorException( "Only composable methods can be annotated with $annotationName", element @@ -88,11 +93,11 @@ internal class ShowkaseValidator(private val environment: XProcessingEnv) { } // This should check if it is an annotation that's annotated with @Preview or @ShowkaseComposable annotation - internal fun checkElementIsAnnotationClass(element: XElement): Boolean { + internal fun checkElementIsAnnotationClass(element: KSAnnotated): Boolean { contract { - returns(true) implies (element is XTypeElement) + returns(true) implies (element is KSClassDeclaration) } - return element.isTypeElement() && element.isAnnotationClass() + return element is KSClassDeclaration && element.classKind == ClassKind.ANNOTATION_CLASS } // We only allow composable functions who's previews meet the following criteria: @@ -102,155 +107,84 @@ internal class ShowkaseValidator(private val environment: XProcessingEnv) { // 2b. At most one parameters is annotated with @PreviewParameter // This is in line with the support that @Preview provides for Android Studio previews. private fun validateComposableParameter( - element: XMethodElement, + element: KSFunctionDeclaration, ): Boolean { // Return true if there are any non-default parameters passed to the composable function or // if there's more than one parameter that's annotated with @PreviewParameter return when { element.parameters.isEmpty() -> false - // If the user is using kapt, we need to leverage kotlin metadata library to get - // information about the default values of the method parameters. This is because - // using "hasDefaultValue" on the parameter returns false for top level functions - // when using kapt. Hence we opted to leverage the kotlin metadata library to add - // proper support. - element.isJavac() && - element.toJavac().enclosingElement.validateKaptComposableParameter(element) -> false - // If the user is using ksp, we had an easier way to do the same check so we avoid - // using the kotlin metadata library and instead rely on the information provided by - // the XProcessing library - !element.isJavac() && element.validateKspComposableParameters() -> false + element.validateComposableParameters() -> false else -> true } } - private fun Element.validateKaptComposableParameter(composableMethodElement: XMethodElement) = - when (val metadata = kotlinMetadata()) { - is KotlinClassMetadata.FileFacade -> metadata.kmPackage.functions.validateKaptComposableParameter( - composableMethodElement - ) - - is KotlinClassMetadata.Class -> metadata.kmClass.functions.validateKaptComposableParameter( - composableMethodElement - ) - - else -> false - } - - private fun MutableList.validateKaptComposableParameter( - composableMethodElement: XMethodElement, - ): Boolean { - // Get the kotlin metadata information for a given composable function being processed. - val composableFunctionMetadata = - this.find { it.name == composableMethodElement.name } ?: return false - - // Divide the parameter list of a given composable function into parameters that are - // not annotated with @PreviewParameter and ones that are annotated with it. - val (nonPreviewParameterParameters, previewParameterParameters) = - composableMethodElement.parameters.partition { - it.getAllAnnotations().none { - it.name == PREVIEW_PARAMETER_SIMPLE_NAME - } - } - - // Get the kotlin metadata information for parameters that are not annotated with @PreviewParameter - val nonPreviewParameterParametersMetadata = - composableFunctionMetadata.valueParameters.filter { metadata -> - nonPreviewParameterParameters.any { metadata.name == it.name } - } - - // Enforce that all parameters have default values and at most one parameters is annotated - // with @PreviewParameter - return nonPreviewParameterParametersMetadata.all { - it.declaresDefaultValue - } && previewParameterParameters.size <= 1 - } - - private fun XMethodElement.validateKspComposableParameters(): Boolean { + private fun KSFunctionDeclaration.validateComposableParameters(): Boolean { // Divide the parameter list of a given composable function into parameters that are // not annotated with @PreviewParameter and ones that are annotated with it. val (nonPreviewParameterParameters, previewParameterParameters) = parameters.partition { parameter -> - parameter.getAllAnnotations().none { annotation -> - annotation.name == PREVIEW_PARAMETER_SIMPLE_NAME + parameter.annotations.none { annotation -> + annotation.shortName.asString() == PREVIEW_PARAMETER_SIMPLE_NAME } } // Enforce that all parameters have default values and at most one parameters is annotated // with @PreviewParameter - return nonPreviewParameterParameters.all { it.hasDefaultValue } && + return nonPreviewParameterParameters.all { it.hasDefault } && previewParameterParameters.size <= 1 } internal fun validateColorElement( - element: XElement, + element: KSAnnotated, annotationName: String ) { contract { - returns() implies (element is XFieldElement) + returns() implies (element is KSPropertyDeclaration) } - if (!element.isField()) { + if (element !is KSPropertyDeclaration) { throw ShowkaseProcessorException( "Only \"Color\" fields can be annotated with $annotationName", element ) } - when (environment.backend) { - XProcessingEnv.Backend.JAVAC -> { - // Kapt can't see that the original type is a value class, it just sees the raw - // type of the Color value class which is a Long - if (element.type.isLong()) return - } - - XProcessingEnv.Backend.KSP -> { - if (element.type.rawType == colorType.rawType) return - } + val resolvedType = element.type.resolve() + if (!resolvedType.isSameTypeAs(colorType)) { + throw ShowkaseProcessorException( + "Only \"Color\" fields can be annotated with $annotationName", + element + ) } - - throw ShowkaseProcessorException( - "Only \"Color\" fields can be annotated with $annotationName", - element - ) - - // TODO(vinay.gaba) Also add the private modifier check. Unfortunately, the java code - // for this element adds a private modifier since it's a field. Potentially use - // kotlinMetadata to enforce this check. } internal fun validateTypographyElement( - element: XElement, + element: KSAnnotated, annotationName: String, - textStyleType: XType, + textStyleType: KSType, ) { contract { - returns() implies (element is XFieldElement) + returns() implies (element is KSPropertyDeclaration) } when { - !element.isField() -> { + element !is KSPropertyDeclaration -> { throw ShowkaseProcessorException( "Only \"TextStyle\" fields can be annotated with $annotationName", element ) } - !element.type.isSameType(textStyleType) -> { + !element.type.resolve().isSameTypeAs(textStyleType) -> { throw ShowkaseProcessorException( "Only \"TextStyle\" fields can be annotated with $annotationName", element ) } - // TODO(vinay.gaba) Also add the private modifier check. Unfortunately, the java code - // for this element adds a private modifier since it's a field. Potentially use - // kotlinMetadata to enforce this check. - else -> { - } } } internal fun validateShowkaseRootElement( - elementSet: Set, - environment: XProcessingEnv + elementSet: Set, ) { if (elementSet.isEmpty()) return @@ -271,8 +205,7 @@ internal class ShowkaseValidator(private val environment: XProcessingEnv) { requireClass(element, showkaseRootAnnotationName) requireInterface( element = element, - interfaceType = environment - .requireType(ShowkaseRootModule::class), + interfaceType = requireType(ShowkaseRootModule::class.qualifiedName!!), annotationName = showkaseRootAnnotationName, ) } @@ -280,13 +213,13 @@ internal class ShowkaseValidator(private val environment: XProcessingEnv) { } private fun requireClass( - element: XElement, + element: KSAnnotated, showkaseRootAnnotationName: String, ) { contract { - returns() implies (element is XTypeElement) + returns() implies (element is KSClassDeclaration) } - if (!element.isTypeElement()) { + if (element !is KSClassDeclaration) { throw ShowkaseProcessorException( "Only classes can be annotated with @$showkaseRootAnnotationName", element @@ -296,13 +229,13 @@ internal class ShowkaseValidator(private val environment: XProcessingEnv) { @Suppress("LongParameterList") private fun requireInterface( - element: XTypeElement, - interfaceType: XType, + element: KSClassDeclaration, + interfaceType: KSType, annotationName: String, ) { - if (!interfaceType.isAssignableFrom(element.type)) { + if (!interfaceType.isAssignableFrom(element.asStarProjectedType())) { throw ShowkaseProcessorException( - "Only an implementation of ${interfaceType.typeName} can be annotated " + + "Only an implementation of ${interfaceType.declaration.qualifiedName?.asString()} can be annotated " + "with @$annotationName", element ) @@ -310,7 +243,7 @@ internal class ShowkaseValidator(private val environment: XProcessingEnv) { } fun validateEnclosingClass( - enclosingClass: XTypeElement?, + enclosingClass: KSClassDeclaration?, ) { if (enclosingClass == null) return @@ -327,8 +260,7 @@ internal class ShowkaseValidator(private val environment: XProcessingEnv) { } internal fun validateShowkaseTestElement( - elements: Collection, - environment: XProcessingEnv, + elements: Collection, ): ScreenshotTestType? { if (elements.isEmpty()) return null @@ -345,8 +277,7 @@ internal class ShowkaseValidator(private val environment: XProcessingEnv) { else -> { // Safe to do this as we've ensured that there's only one element in this set val element = elements.first() - val showkaseScreenshotTestTypeMirror = environment - .requireType(SHOWKASE_SCREENSHOT_TEST_CLASS_NAME) + val showkaseScreenshotTestTypeMirror = requireType(SHOWKASE_SCREENSHOT_TEST_CLASS_NAME) // Validate that the class annotated with @ShowkaseScreenshotTest is an abstract/open // class @@ -355,27 +286,29 @@ internal class ShowkaseValidator(private val environment: XProcessingEnv) { // Validate that the class annotated with @ShowkaseScreenshot extends the // ShowkaseScreenshotTest interface val isShowkaseScreenshotTest = - showkaseScreenshotTestTypeMirror.isAssignableFrom(element.type) + showkaseScreenshotTestTypeMirror.isAssignableFrom(element.asStarProjectedType()) return if (isShowkaseScreenshotTest) { ScreenshotTestType.SHOWKASE } else if ( - environment.findType(PAPARAZZI_SHOWKASE_SCREENSHOT_TEST_CLASS_NAME) - ?.isAssignableFrom(element.type) == true + findType(ROBORAZZI_SHOWKASE_SCREENSHOT_TEST_CLASS_NAME) + ?.isAssignableFrom(element.asStarProjectedType()) == true ) { - val paparazziShowkaseScreenshotTestTypeMirror = environment - .requireType(PAPARAZZI_SHOWKASE_SCREENSHOT_TEST_CLASS_NAME) - validatePaparazziShowkaseScreenshotTest( - environment, element, - paparazziShowkaseScreenshotTestTypeMirror + val roborazziShowkaseScreenshotTestTypeMirror = requireType( + ROBORAZZI_SHOWKASE_SCREENSHOT_TEST_CLASS_NAME + ) + validateRoborazziShowkaseScreenshotTest( + element, + roborazziShowkaseScreenshotTestTypeMirror ) - ScreenshotTestType.PAPARAZZI_SHOWKASE + ScreenshotTestType.ROBORAZZI_SHOWKASE } else { throw ShowkaseProcessorException( "Only an implementation of com.airbnb.android.showkase.screenshot.testing" + - ".ShowkaseScreenshotTest or com.airbnb.android.showkase.screenshot" + - ".testing.paparazzi.PaparazziShowkaseScreenshotTest can be annotated " + + ".ShowkaseScreenshotTest or " + + "com.airbnb.android.showkase.screenshot.testing.roborazzi" + + ".RoborazziShowkaseScreenshotTest can be annotated " + "with @$showkaseScreenshotAnnotationName", element ) @@ -387,43 +320,40 @@ internal class ShowkaseValidator(private val environment: XProcessingEnv) { } } - private fun validatePaparazziShowkaseScreenshotTest( - environment: XProcessingEnv, - element: XTypeElement, - paparazziShowkaseScreenshotTestTypeMirror: XType + private fun validateRoborazziShowkaseScreenshotTest( + element: KSClassDeclaration, + roborazziShowkaseScreenshotTestTypeMirror: KSType ) { - val paparazziShowkaseScreenshotTestCompanionType = environment - .requireType(PAPARAZZI_SHOWKASE_SCREENSHOT_TEST_COMPANION_CLASS_NAME) - - val companionObjectTypeElements = element.getEnclosedTypeElements().filter { - it.isCompanionObject() - } + val roborazziCompanionType = + requireType(ROBORAZZI_SHOWKASE_SCREENSHOT_TEST_COMPANION_CLASS_NAME) + + val companionObjectTypeElements = element.declarations + .filterIsInstance() + .filter { it.isCompanionObject } + .toList() + val screenshotTestName = + roborazziShowkaseScreenshotTestTypeMirror.declaration.qualifiedName?.asString() + val companionName = + roborazziCompanionType.declaration.qualifiedName?.asString() val errorMessage = - "Classes implementing the ${paparazziShowkaseScreenshotTestTypeMirror.typeName} " + + "Classes implementing the $screenshotTestName " + "interface should have a companion object that implements the " + - "${paparazziShowkaseScreenshotTestCompanionType.typeName} interface." + "$companionName interface." if (companionObjectTypeElements.isEmpty()) { - throw ShowkaseProcessorException( - errorMessage, - element - ) + throw ShowkaseProcessorException(errorMessage, element) } - - if (!paparazziShowkaseScreenshotTestCompanionType - .isAssignableFrom(companionObjectTypeElements[0].type) + if (!roborazziCompanionType + .isAssignableFrom(companionObjectTypeElements[0].asStarProjectedType()) ) { - throw ShowkaseProcessorException( - errorMessage, - element - ) + throw ShowkaseProcessorException(errorMessage, element) } } private fun requireOpenClass( - element: XTypeElement, + element: KSClassDeclaration, annotationName: String, ) { - if (element.isFinal()) { + if (!element.isOpen()) { throw ShowkaseProcessorException( "Class annotated with $annotationName needs to be an abstract/open class.", element @@ -454,9 +384,9 @@ internal class ShowkaseValidator(private val environment: XProcessingEnv) { companion object { private const val SHOWKASE_SCREENSHOT_TEST_CLASS_NAME = "com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotTest" - private const val PAPARAZZI_SHOWKASE_SCREENSHOT_TEST_CLASS_NAME = - "com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest" - private const val PAPARAZZI_SHOWKASE_SCREENSHOT_TEST_COMPANION_CLASS_NAME = - "com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest.CompanionObject" + private const val ROBORAZZI_SHOWKASE_SCREENSHOT_TEST_CLASS_NAME = + "com.airbnb.android.showkase.screenshot.testing.roborazzi.RoborazziShowkaseScreenshotTest" + private const val ROBORAZZI_SHOWKASE_SCREENSHOT_TEST_COMPANION_CLASS_NAME = + "com.airbnb.android.showkase.screenshot.testing.roborazzi.RoborazziShowkaseScreenshotTest.CompanionObject" } } diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/models/ShowkaseMetadata.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/models/ShowkaseMetadata.kt index 77061c722..4f1048fce 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/models/ShowkaseMetadata.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/models/ShowkaseMetadata.kt @@ -1,36 +1,49 @@ package com.airbnb.android.showkase.processor.models -import androidx.room.compiler.processing.XAnnotation -import androidx.room.compiler.processing.XElement -import androidx.room.compiler.processing.XFieldElement -import androidx.room.compiler.processing.XMemberContainer -import androidx.room.compiler.processing.XMethodElement -import androidx.room.compiler.processing.XType -import androidx.room.compiler.processing.XTypeElement -import androidx.room.compiler.processing.compat.XConverters.toJavac import com.airbnb.android.showkase.annotation.ScreenshotCaptureConfig import com.airbnb.android.showkase.annotation.ScreenshotCaptureType import com.airbnb.android.showkase.annotation.ScreenshotConfig import com.airbnb.android.showkase.annotation.ShowkaseColor import com.airbnb.android.showkase.annotation.ShowkaseComposable +import com.airbnb.android.showkase.annotation.ShowkaseDialog import com.airbnb.android.showkase.annotation.ShowkaseMultiPreviewCodegenMetadata import com.airbnb.android.showkase.annotation.ShowkaseTypography import com.airbnb.android.showkase.processor.ShowkaseProcessor.Companion.PREVIEW_PARAMETER_SIMPLE_NAME import com.airbnb.android.showkase.processor.ShowkaseProcessor.Companion.PREVIEW_SIMPLE_NAME import com.airbnb.android.showkase.processor.exceptions.ShowkaseProcessorException import com.airbnb.android.showkase.processor.logging.ShowkaseValidator +import com.airbnb.android.showkase.processor.utils.annotationDeclaration import com.airbnb.android.showkase.processor.utils.findAnnotationBySimpleName -import com.airbnb.android.showkase.processor.utils.getFieldWithReflection +import com.airbnb.android.showkase.processor.utils.getAnnotation +import com.airbnb.android.showkase.processor.utils.getAnnotations +import com.airbnb.android.showkase.processor.utils.getAsAnnotation +import com.airbnb.android.showkase.processor.utils.getAsBoolean +import com.airbnb.android.showkase.processor.utils.getAsEnum +import com.airbnb.android.showkase.processor.utils.getAsInt +import com.airbnb.android.showkase.processor.utils.getAsIntList +import com.airbnb.android.showkase.processor.utils.getAsString +import com.airbnb.android.showkase.processor.utils.getAsStringList +import com.airbnb.android.showkase.processor.utils.getAsType +import com.airbnb.android.showkase.processor.utils.getAsTypeList +import com.airbnb.android.showkase.processor.utils.requireAnnotation import com.airbnb.android.showkase.processor.utils.requireAnnotationBySimpleName +import com.google.devtools.ksp.symbol.ClassKind +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSDeclaration +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.google.devtools.ksp.symbol.KSType import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.TypeName -import com.squareup.kotlinpoet.javapoet.toKClassName -import com.squareup.kotlinpoet.javapoet.toKTypeName +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.toTypeName import java.util.Locale @Suppress("LongParameterList") internal sealed class ShowkaseMetadata { - abstract val element: XElement + abstract val element: KSAnnotated abstract val packageName: String abstract val packageSimpleName: String abstract val elementName: String @@ -46,7 +59,7 @@ internal sealed class ShowkaseMetadata { get() = enclosingClassName?.let { "${it}_$elementName" } ?: "${packageName}_$elementName" data class Component( - override val element: XElement, + override val element: KSAnnotated, override val packageName: String, override val packageSimpleName: String, override val elementName: String, @@ -66,10 +79,13 @@ internal sealed class ShowkaseMetadata { val tags: List = emptyList(), val extraMetadata: List = emptyList(), val screenshotConfig: ScreenshotConfig = ScreenshotConfig.SingleStaticImage, + val isDialog: Boolean = false, + val dialogButtonText: String = "", + val dialogHideButtonText: String = "", ) : ShowkaseMetadata() data class Color( - override val element: XElement, + override val element: KSAnnotated, override val packageSimpleName: String, override val packageName: String, override val elementName: String, @@ -82,7 +98,7 @@ internal sealed class ShowkaseMetadata { ) : ShowkaseMetadata() data class Typography( - override val element: XElement, + override val element: KSAnnotated, override val packageSimpleName: String, override val packageName: String, override val elementName: String, @@ -111,7 +127,7 @@ internal enum class ShowkaseMetadataType { TYPOGRAPHY } -internal fun XAnnotation.getCodegenMetadataTypes(): Pair { +internal fun KSAnnotation.getCodegenMetadataTypes(): Pair { return Pair( getAsTypeList("enclosingClass").firstOrNull(), getAsTypeList("previewParameterClass").firstOrNull() @@ -124,10 +140,11 @@ private fun Int.parseAnnotationProperty() = when (this) { } internal fun getShowkaseMetadata( - element: XMethodElement, + element: KSFunctionDeclaration, showkaseValidator: ShowkaseValidator ): List { val showkaseAnnotations = element.getAnnotations(ShowkaseComposable::class) + val elementName = element.simpleName.asString() val commonMetadata = element.extractCommonMetadata(showkaseValidator) val previewParameterMetadata = element.getPreviewParameterMetadata() @@ -136,7 +153,7 @@ internal fun getShowkaseMetadata( // If this component was configured to be skipped, return early if (annotation.getAsBoolean("skip")) return@mapNotNull null - val showkaseName = getShowkaseName(annotation.getAsString("name"), element.name) + val showkaseName = getShowkaseName(annotation.getAsString("name"), elementName) val showkaseGroup = getShowkaseGroup( annotation.getAsString("group"), commonMetadata.enclosingClass, @@ -150,7 +167,7 @@ internal fun getShowkaseMetadata( packageSimpleName = commonMetadata.moduleName, packageName = commonMetadata.packageName, enclosingClassName = commonMetadata.enclosingClassName, - elementName = element.name, + elementName = elementName, showkaseName = showkaseName, showkaseGroup = showkaseGroup, showkaseStyleName = showkaseStyleName, @@ -171,11 +188,63 @@ internal fun getShowkaseMetadata( } } -private fun screenshotConfigFrom(annotation: XAnnotation): ScreenshotConfig { +internal fun getShowkaseDialogMetadata( + element: KSFunctionDeclaration, + showkaseValidator: ShowkaseValidator +): List { + val showkaseDialogAnnotations = element.getAnnotations(ShowkaseDialog::class) + val elementName = element.simpleName.asString() + + val commonMetadata = element.extractCommonMetadata(showkaseValidator) + val previewParameterMetadata = element.getPreviewParameterMetadata() + + return showkaseDialogAnnotations.mapNotNull { annotation -> + if (annotation.getAsBoolean("skip")) return@mapNotNull null + + val showkaseName = getShowkaseName(annotation.getAsString("name"), elementName) + val showkaseGroup = getShowkaseGroup( + annotation.getAsString("group"), + commonMetadata.enclosingClass, + ) + val isDefaultStyle = annotation.getAsBoolean("defaultStyle") + val showkaseStyleName = + getShowkaseStyleName(annotation.getAsString("styleName"), isDefaultStyle) + val tags = annotation.getAsStringList("tags") + val extraMetadata = annotation.getAsStringList("extraMetadata") + val screenshotConfig = screenshotConfigFrom(annotation) + ShowkaseMetadata.Component( + packageSimpleName = commonMetadata.moduleName, + packageName = commonMetadata.packageName, + enclosingClassName = commonMetadata.enclosingClassName, + elementName = elementName, + showkaseName = showkaseName, + showkaseGroup = showkaseGroup, + showkaseStyleName = showkaseStyleName, + showkaseWidthDp = annotation.getAsInt("widthDp").parseAnnotationProperty(), + showkaseHeightDp = annotation.getAsInt("heightDp").parseAnnotationProperty(), + insideObject = commonMetadata.showkaseFunctionType.insideObject(), + insideWrapperClass = commonMetadata.showkaseFunctionType == ShowkaseFunctionType.INSIDE_CLASS, + element = element, + showkaseKDoc = commonMetadata.kDoc, + previewParameterName = previewParameterMetadata?.first, + previewParameterProviderType = previewParameterMetadata?.second, + isDefaultStyle = isDefaultStyle, + componentIndex = showkaseDialogAnnotations.indexOf(annotation), + tags = tags, + extraMetadata = extraMetadata, + screenshotConfig = screenshotConfig, + isDialog = true, + dialogButtonText = annotation.getAsString("buttonText"), + dialogHideButtonText = annotation.getAsString("hideButtonText"), + ) + } +} + +private fun screenshotConfigFrom(annotation: KSAnnotation): ScreenshotConfig { val screenshotCaptureConfig = annotation.getAsAnnotation(ShowkaseComposable::screenshotCaptureConfig.name) val screenshotCaptureType = ScreenshotCaptureType.valueOf( - screenshotCaptureConfig.getAsEnum(ScreenshotCaptureConfig::type.name).name + screenshotCaptureConfig.getAsEnum(ScreenshotCaptureConfig::type.name).name ) val gifDurationMillis = screenshotCaptureConfig.getAsInt(ScreenshotCaptureConfig::durationMillis.name) @@ -198,26 +267,28 @@ private fun screenshotConfigFrom(annotation: XAnnotation): ScreenshotConfig { return screenshotConfig } -internal fun XMethodElement.extractCommonMetadata(showkaseValidator: ShowkaseValidator): CommonMetadata { - return extractCommonMetadata(enclosingElement, showkaseValidator) +internal fun KSFunctionDeclaration.extractCommonMetadata(showkaseValidator: ShowkaseValidator): CommonMetadata { + return extractCommonMetadataFromDeclaration(this, showkaseValidator) } -internal fun XFieldElement.extractCommonMetadata(showkaseValidator: ShowkaseValidator): CommonMetadata { - return extractCommonMetadata(enclosingElement, showkaseValidator) +internal fun KSPropertyDeclaration.extractCommonMetadata(showkaseValidator: ShowkaseValidator): CommonMetadata { + return extractCommonMetadataFromDeclaration(this, showkaseValidator) } -internal fun XElement.extractCommonMetadata( - enclosingElement: XMemberContainer, - showkaseValidator: ShowkaseValidator +private fun extractCommonMetadataFromDeclaration( + declaration: KSDeclaration, + showkaseValidator: ShowkaseValidator, ): CommonMetadata { - val showkaseFunctionType: ShowkaseFunctionType = getShowkaseFunctionType(enclosingElement) + val parent = declaration.parentDeclaration + val showkaseFunctionType = getShowkaseFunctionType(declaration, parent) + val packageName = declaration.packageName.asString() return CommonMetadata( - packageName = enclosingElement.className.packageName(), - moduleName = enclosingElement.className.packageName().substringAfterLast("."), - kDoc = docComment.orEmpty().trim(), + packageName = packageName, + moduleName = packageName.substringAfterLast("."), + kDoc = declaration.docString.orEmpty().trim(), showkaseFunctionType = showkaseFunctionType, - enclosingClass = getEnclosingClass(showkaseFunctionType, enclosingElement) + enclosingClass = getEnclosingClass(showkaseFunctionType, parent) ).also { showkaseValidator.validateEnclosingClass(it.enclosingClass) } @@ -228,17 +299,18 @@ internal data class CommonMetadata( val moduleName: String, val kDoc: String, val showkaseFunctionType: ShowkaseFunctionType, - val enclosingClass: XTypeElement?, + val enclosingClass: KSClassDeclaration?, ) { - val enclosingClassName: ClassName? = enclosingClass?.className?.toKClassName() + val enclosingClassName: ClassName? = enclosingClass?.toClassName() } @Suppress("LongParameterList", "LongMethod") internal fun getShowkaseMetadataFromPreview( - element: XMethodElement, + element: KSFunctionDeclaration, showkaseValidator: ShowkaseValidator, ): List { val previewAnnotations = element.requireAnnotationBySimpleName(PREVIEW_SIMPLE_NAME) + val elementName = element.simpleName.asString() val showkaseComosableAnnotation = element.getAnnotation(ShowkaseComposable::class) // If this component was configured to be skipped, return early @@ -249,7 +321,7 @@ internal fun getShowkaseMetadataFromPreview( val commonMetadata = element.extractCommonMetadata(showkaseValidator) val showkaseName = getShowkaseName( annotation.getAsString("name"), - element.name + elementName ) val showkaseGroup = getShowkaseGroup( annotation.getAsString("group"), @@ -265,7 +337,7 @@ internal fun getShowkaseMetadataFromPreview( packageSimpleName = commonMetadata.moduleName, packageName = commonMetadata.packageName, enclosingClassName = commonMetadata.enclosingClassName, - elementName = element.name, + elementName = elementName, showkaseKDoc = commonMetadata.kDoc, showkaseName = showkaseName, showkaseGroup = showkaseGroup, @@ -282,14 +354,15 @@ internal fun getShowkaseMetadataFromPreview( } internal fun getShowkaseMetadataFromCustomAnnotation( - element: XMethodElement, + element: KSFunctionDeclaration, showkaseValidator: ShowkaseValidator, annotationName: String, ): List { val customAnnotation = element.requireAnnotationBySimpleName(annotationName) + val elementName = element.simpleName.asString() val previewAnnotations = customAnnotation.map { - it.typeElement.requireAnnotationBySimpleName(PREVIEW_SIMPLE_NAME) + it.annotationDeclaration().requireAnnotationBySimpleName(PREVIEW_SIMPLE_NAME) }.flatten() val showkaseComosableAnnotation = element.getAnnotation(ShowkaseComposable::class) @@ -304,7 +377,7 @@ internal fun getShowkaseMetadataFromCustomAnnotation( val annotationHasName = annotationNameParam.isNotEmpty() val showkaseNameFromAnnotation = if (annotationHasName) annotationNameParam else index - val showkaseName = "${element.name} - $showkaseNameFromAnnotation" + val showkaseName = "$elementName - $showkaseNameFromAnnotation" val showkaseGroup = getShowkaseGroup( annotation.getAsString("group"), commonMetadata.enclosingClass, @@ -319,7 +392,7 @@ internal fun getShowkaseMetadataFromCustomAnnotation( packageSimpleName = commonMetadata.moduleName, packageName = commonMetadata.packageName, enclosingClassName = commonMetadata.enclosingClassName, - elementName = element.name, + elementName = elementName, showkaseKDoc = commonMetadata.kDoc, showkaseName = showkaseName, showkaseGroup = showkaseGroup, @@ -336,7 +409,7 @@ internal fun getShowkaseMetadataFromCustomAnnotation( } internal fun getShowkaseMetadata( - xElement: XMethodElement, + xElement: KSFunctionDeclaration, customPreviewMetadata: ShowkaseMultiPreviewCodegenMetadata, elementIndex: Int, index: Int, @@ -356,13 +429,14 @@ internal fun getShowkaseMetadata( } else { customPreviewMetadata.showkaseWidth } + val elementName = xElement.simpleName.asString() return ShowkaseMetadata.Component( element = xElement, - elementName = xElement.name, + elementName = elementName, packageName = commonMetadata.packageName, packageSimpleName = commonMetadata.moduleName, - showkaseName = "${xElement.name} - ${customPreviewMetadata.previewName} - $elementIndex", + showkaseName = "$elementName - ${customPreviewMetadata.previewName} - $elementIndex", insideObject = commonMetadata.showkaseFunctionType.insideObject(), previewParameterName = previewParamMetadata?.first, previewParameterProviderType = previewParamMetadata?.second, @@ -379,26 +453,24 @@ internal fun getShowkaseMetadata( ) } -private fun XMethodElement.getPreviewParameterMetadata(): Pair? { +private fun KSFunctionDeclaration.getPreviewParameterMetadata(): Pair? { val previewParameterPair = getPreviewParameterAnnotation() return previewParameterPair?.let { - it.first to it.second.getAsType("provider") - .typeName - .toKTypeName() + it.first to it.second.getAsType("provider").toTypeName() } } -private fun XMethodElement.getPreviewParameterAnnotation(): Pair? { +private fun KSFunctionDeclaration.getPreviewParameterAnnotation(): Pair? { return parameters.mapNotNull { parameter -> val previewParamAnnotation = parameter.findAnnotationBySimpleName(PREVIEW_PARAMETER_SIMPLE_NAME) previewParamAnnotation?.let { - parameter.name to previewParamAnnotation + (parameter.name?.asString() ?: "") to previewParamAnnotation } }.firstOrNull() } internal fun getShowkaseColorMetadata( - element: XFieldElement, + element: KSPropertyDeclaration, showkaseValidator: ShowkaseValidator ): ShowkaseMetadata { val showkaseColorAnnotation = element.requireAnnotation(ShowkaseColor::class) @@ -406,7 +478,8 @@ internal fun getShowkaseColorMetadata( // because the properties are generated outside the companion object in java land(as opposed to // inside the companion class for functions). Need to investigate more. val commonMetadata = element.extractCommonMetadata(showkaseValidator) - val showkaseName = getShowkaseName(showkaseColorAnnotation.getAsString("name"), element.name) + val elementName = element.simpleName.asString() + val showkaseName = getShowkaseName(showkaseColorAnnotation.getAsString("name"), elementName) val showkaseGroup = getShowkaseGroup(showkaseColorAnnotation.getAsString("group"), commonMetadata.enclosingClass) return ShowkaseMetadata.Color( @@ -414,7 +487,7 @@ internal fun getShowkaseColorMetadata( showkaseName = showkaseName, showkaseGroup = showkaseGroup, showkaseKDoc = commonMetadata.kDoc, - elementName = element.name, + elementName = elementName, packageSimpleName = commonMetadata.moduleName, packageName = commonMetadata.packageName, enclosingClassName = commonMetadata.enclosingClassName, @@ -424,16 +497,17 @@ internal fun getShowkaseColorMetadata( } internal fun getShowkaseTypographyMetadata( - element: XFieldElement, + element: KSPropertyDeclaration, showkaseValidator: ShowkaseValidator ): ShowkaseMetadata { val showkaseTypographyAnnotation = element.requireAnnotation(ShowkaseTypography::class) val commonMetadata = element.extractCommonMetadata(showkaseValidator) + val elementName = element.simpleName.asString() // TODO(vinaygaba): Typography properties aren't working properly with companion objects. // This is because the properties are generated outside the companion object in java land(as // opposed to inside the companion class for functions). Need to investigate more. - val showkaseName = getShowkaseName(showkaseTypographyAnnotation.getAsString("name"), element.name) + val showkaseName = getShowkaseName(showkaseTypographyAnnotation.getAsString("name"), elementName) val showkaseGroup = getShowkaseGroup(showkaseTypographyAnnotation.getAsString("group"), commonMetadata.enclosingClass) @@ -442,7 +516,7 @@ internal fun getShowkaseTypographyMetadata( showkaseName = showkaseName, showkaseGroup = showkaseGroup, showkaseKDoc = commonMetadata.kDoc, - elementName = element.name, + elementName = elementName, packageSimpleName = commonMetadata.moduleName, packageName = commonMetadata.packageName, enclosingClassName = commonMetadata.enclosingClassName, @@ -451,66 +525,33 @@ internal fun getShowkaseTypographyMetadata( ) } -internal fun XElement.getShowkaseFunctionType(enclosingElement: XMemberContainer): ShowkaseFunctionType { +internal fun getShowkaseFunctionType( + declaration: KSDeclaration, + parent: KSDeclaration? +): ShowkaseFunctionType { return when { - this.isTopLevel(enclosingElement) -> ShowkaseFunctionType.TOP_LEVEL - (enclosingElement as? XTypeElement)?.isCompanionObject() == true -> - ShowkaseFunctionType.INSIDE_COMPANION_OBJECT - - (enclosingElement as? XTypeElement)?.isKotlinObject() == true -> ShowkaseFunctionType.INSIDE_OBJECT - enclosingElement is XTypeElement -> ShowkaseFunctionType.INSIDE_CLASS + parent !is KSClassDeclaration -> ShowkaseFunctionType.TOP_LEVEL + parent.isCompanionObject -> ShowkaseFunctionType.INSIDE_COMPANION_OBJECT + parent.classKind == ClassKind.OBJECT -> ShowkaseFunctionType.INSIDE_OBJECT + parent.classKind == ClassKind.CLASS || parent.classKind == ClassKind.INTERFACE -> + ShowkaseFunctionType.INSIDE_CLASS else -> throw ShowkaseProcessorException( "Function is declared in a way that is not supported by Showkase.", - this + declaration ) } } -fun XElement.isTopLevel(enclosingElement: XMemberContainer): Boolean { - return if (isJavac()) { - // Per enclosingElement kdoc: - // When running with KAPT, the value will be an XTypeElement. - // Right now xprocessing doesn't expose the top level details, so we have to use - // reflection to get kotlin metadata - val xTypeElement = enclosingElement as? XTypeElement - ?: throw ShowkaseProcessorException( - "Expected a type element but got $enclosingElement", - this - ) - - // JavacTypeElement has a kotlinMetadata property with a custom "KotlinMetadataElement" - // class type. This is null though if the type doesn't have metadata, such as in the case - // of a top level function. - val kotlinMetadata = xTypeElement.getFieldWithReflection("kotlinMetadata") - - return kotlinMetadata == null - } else { - // Per enclosingElement kdoc: - // When running with KSP, if this function is in source, the value will NOT be an XTypeElement. - // We don't expect to handle functions from classpath because we only process annotations in source - enclosingElement !is XTypeElement - } -} - -fun XElement.isJavac(): Boolean { - @Suppress("TooGenericExceptionCaught") - return try { - toJavac() - true - } catch (e: Throwable) { - false - } -} - internal fun getEnclosingClass( showkaseFunctionType: ShowkaseFunctionType, - enclosingElement: XMemberContainer -): XTypeElement? = when (showkaseFunctionType) { + parent: KSDeclaration?, +): KSClassDeclaration? = when (showkaseFunctionType) { ShowkaseFunctionType.TOP_LEVEL -> null - ShowkaseFunctionType.INSIDE_CLASS, ShowkaseFunctionType.INSIDE_OBJECT -> enclosingElement as XTypeElement + ShowkaseFunctionType.INSIDE_CLASS, ShowkaseFunctionType.INSIDE_OBJECT -> parent as KSClassDeclaration // Get the class that holds the companion object instead of using the intermediate element // that's used to represent the companion object. - ShowkaseFunctionType.INSIDE_COMPANION_OBJECT -> (enclosingElement as XTypeElement).enclosingTypeElement + ShowkaseFunctionType.INSIDE_COMPANION_OBJECT -> + (parent as KSClassDeclaration).parentDeclaration as? KSClassDeclaration } internal fun getShowkaseName( @@ -523,12 +564,11 @@ internal fun getShowkaseName( internal fun getShowkaseGroup( showkaseGroupFromAnnotation: String, - enclosingClass: XTypeElement?, + enclosingClass: KSClassDeclaration?, ) = when { showkaseGroupFromAnnotation.isNotBlank() -> showkaseGroupFromAnnotation - showkaseGroupFromAnnotation.isBlank() && enclosingClass != null -> enclosingClass.name.capitalize( - Locale.getDefault() - ) + showkaseGroupFromAnnotation.isBlank() && enclosingClass != null -> + enclosingClass.simpleName.asString().capitalize(Locale.getDefault()) else -> "Default Group" } diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/KotlinMetadataUtils.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/KotlinMetadataUtils.kt deleted file mode 100644 index 9acf00caf..000000000 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/KotlinMetadataUtils.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.airbnb.android.showkase.processor.utils - -import kotlin.metadata.jvm.KotlinClassMetadata -import javax.lang.model.element.Element - -internal fun Element.kotlinMetadata(): KotlinClassMetadata? { - // https://github.com/JetBrains/kotlin/tree/master/libraries/kotlinx-metadata/jvm - val kotlinMetadataAnnotation = getAnnotation(Metadata::class.java) ?: return null - return KotlinClassMetadata.readStrict(kotlinMetadataAnnotation) -} diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/KsAnnotationExtensions.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/KsAnnotationExtensions.kt new file mode 100644 index 000000000..c4ada45ef --- /dev/null +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/KsAnnotationExtensions.kt @@ -0,0 +1,130 @@ +package com.airbnb.android.showkase.processor.utils + +import com.airbnb.android.showkase.processor.exceptions.ShowkaseProcessorException +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSDeclaration +import com.google.devtools.ksp.symbol.KSFile +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSType +import kotlin.reflect.KClass + +internal fun KSAnnotation.argByNameOrNull(name: String): Any? { + val argument = arguments.firstOrNull { it.name?.asString() == name } + ?: defaultArguments.firstOrNull { it.name?.asString() == name } + ?: return null + return argument.value +} + +internal fun KSAnnotation.argByName(name: String): Any = + argByNameOrNull(name) ?: throw ShowkaseProcessorException( + "Annotation @${shortName.asString()} is missing required argument '$name'" + ) + +internal fun KSAnnotation.getAsString(name: String): String = argByName(name) as String + +internal fun KSAnnotation.getAsInt(name: String): Int = argByName(name) as Int + +internal fun KSAnnotation.getAsBoolean(name: String): Boolean = argByName(name) as Boolean + +@Suppress("UNCHECKED_CAST") +internal fun KSAnnotation.getAsStringList(name: String): List { + return when (val value = argByNameOrNull(name)) { + null -> emptyList() + is List<*> -> value as List + is Array<*> -> value.map { it as String } + else -> error("Expected list of strings for $name, got ${value::class}") + } +} + +@Suppress("UNCHECKED_CAST") +internal fun KSAnnotation.getAsIntList(name: String): List { + return when (val value = argByNameOrNull(name)) { + null -> emptyList() + is List<*> -> value as List + is IntArray -> value.toList() + is Array<*> -> value.map { it as Int } + else -> error("Expected list of ints for $name, got ${value::class}") + } +} + +internal fun KSAnnotation.getAsAnnotation(name: String): KSAnnotation = argByName(name) as KSAnnotation + +internal fun KSAnnotation.getAsType(name: String): KSType = argByName(name) as KSType + +@Suppress("UNCHECKED_CAST") +internal fun KSAnnotation.getAsTypeList(name: String): List { + return when (val value = argByNameOrNull(name)) { + null -> emptyList() + is List<*> -> value as List + is Array<*> -> value.map { it as KSType } + else -> error("Expected list of types for $name, got ${value::class}") + } +} + +internal inline fun > KSAnnotation.getAsEnum(name: String): E { + val value = argByName(name) + val entryName = when (value) { + is KSType -> value.declaration.simpleName.asString() + is KSClassDeclaration -> value.simpleName.asString() + else -> value.toString() + } + return enumValueOf(entryName) +} + +internal fun KSAnnotated.findAnnotationBySimpleName(simpleName: String): KSAnnotation? { + return annotations.firstOrNull { it.shortName.asString() == simpleName } +} + +internal fun KSAnnotated.requireAnnotationBySimpleName(simpleName: String): List { + return annotations.filter { it.shortName.asString() == simpleName }.toList() +} + +internal fun KSAnnotated.getAnnotation(kclass: KClass): KSAnnotation? = + findAnnotationBySimpleName(kclass.simpleName!!) + +internal fun KSAnnotated.getAnnotations(kclass: KClass): List = + requireAnnotationBySimpleName(kclass.simpleName!!) + +internal fun KSAnnotated.requireAnnotation(kclass: KClass): KSAnnotation = + findAnnotationBySimpleName(kclass.simpleName!!) + ?: error("Annotation @${kclass.simpleName} not found on $this") + +internal fun KSAnnotation.annotationDeclaration(): KSClassDeclaration = + annotationType.resolve().declaration as KSClassDeclaration + +internal fun KSAnnotated.containingFileOrNull(): KSFile? = + (this as? KSDeclaration)?.containingFile + +internal fun KSType.isSameTypeAs(other: KSType): Boolean { + val thisName = this.declaration.qualifiedName?.asString() + val otherName = other.declaration.qualifiedName?.asString() + return thisName != null && + otherName != null && + thisName == otherName && + this.arguments.size == other.arguments.size && + this.arguments.zip(other.arguments).all { (a, b) -> + a.type?.resolve()?.declaration?.qualifiedName?.asString() == + b.type?.resolve()?.declaration?.qualifiedName?.asString() + } +} + +/** + * The order of symbols returned by KSP2 differs from that returned by KSP1. + * This workaround ensures that the order of symbols is consistent across both KSP versions. + * + * @see [https://github.com/google/ksp/issues/1719] + */ +internal fun Collection.ensureConsistentOrdering(): Sequence { + return this.asSequence() + .sortedWith( + compareBy { element -> + when (element) { + is KSClassDeclaration -> 0 + is KSFunctionDeclaration -> 1 + else -> 2 + } + } + ) +} diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/ReflectionUtils.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/ReflectionUtils.kt deleted file mode 100644 index 53c429d71..000000000 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/ReflectionUtils.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.airbnb.android.showkase.processor.utils - -/** - * Easy way to retrieve the value of a field via reflection. - * - * @param fieldName Name of the field on this class - * @param U The type of the field.. - */ -inline fun Any.getFieldWithReflection(fieldName: String): U { - - val field = (javaClass.fields + javaClass.declaredFields).firstOrNull { - it.name == fieldName - } - - val value = if (field != null) { - field.isAccessible = true - field.get(this) - } else { - val methodName = "get${fieldName.capitalize()}" - // Kotlin sometimes does not have a field backing a property, so we try a getter method - // for it. - val method = (javaClass.methods + javaClass.declaredMethods).firstOrNull { - it.name == methodName - } - - if (method != null) { - method.isAccessible = true - method.invoke(this) - } else { - error("Field named $fieldName not found on $javaClass") - } - } - - check(value is U) { - "Expected field '$fieldName' to be ${U::class.java.simpleName} but got a ${value.javaClass.simpleName}" - } - @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") - return value as U -} - -inline fun Any.getFieldWithReflectionOrNull(fieldName: String): U? { - return kotlin.runCatching { - getFieldWithReflection(fieldName) - }.getOrNull() -} diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/XProcessingExtensions.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/XProcessingExtensions.kt deleted file mode 100644 index 7470921a4..000000000 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/utils/XProcessingExtensions.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.airbnb.android.showkase.processor.utils - -import androidx.room.compiler.processing.XAnnotated -import androidx.room.compiler.processing.XAnnotation -import androidx.room.compiler.processing.XElement -import androidx.room.compiler.processing.isMethod -import androidx.room.compiler.processing.isTypeElement - -fun XAnnotated.findAnnotationBySimpleName(simpleName: String): XAnnotation? { - return getAllAnnotations().find { it.name == simpleName } -} - -fun XAnnotated.requireAnnotationBySimpleName(simpleName: String): List { - return getAllAnnotations().filter { it.name == simpleName } -} - -/** - * The order of symbols returns by KSP2 differs from that returned by KSP1. - * This workaround ensure that the order of symbols is consistent across both KSP versions. - * - * @see [https://github.com/google/ksp/issues/1719] - * */ -internal fun Collection.ensureConsistentOrdering(): Sequence { - return this.asSequence() - .sortedWith( - compareBy { element -> - when { - element.isTypeElement() -> 0 - element.isMethod() -> 1 - else -> 2 - } - } - ) -} diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/PaparazziShowkaseScreenshotTestWriter.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/PaparazziShowkaseScreenshotTestWriter.kt deleted file mode 100644 index 4b39dd4fb..000000000 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/PaparazziShowkaseScreenshotTestWriter.kt +++ /dev/null @@ -1,270 +0,0 @@ -package com.airbnb.android.showkase.processor.writer - -import androidx.room.compiler.processing.XFiler -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.writeTo -import com.squareup.kotlinpoet.AnnotationSpec -import com.squareup.kotlinpoet.ClassName -import com.squareup.kotlinpoet.CodeBlock -import com.squareup.kotlinpoet.FunSpec -import com.squareup.kotlinpoet.KModifier -import com.squareup.kotlinpoet.LIST -import com.squareup.kotlinpoet.ParameterSpec -import com.squareup.kotlinpoet.ParameterizedTypeName -import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import com.squareup.kotlinpoet.PropertySpec -import com.squareup.kotlinpoet.TypeSpec - -class PaparazziShowkaseScreenshotTestWriter(private val environment: XProcessingEnv) { - @Suppress("LongParameterList") - internal fun generateScreenshotTests( - screenshotTestPackageName: String, - rootModulePackageName: String, - testClassName: String - ) { - val showkaseScreenshotTestClassName = "${testClassName}Impl" - val fileBuilder = getFileBuilder(screenshotTestPackageName, showkaseScreenshotTestClassName) - fileBuilder - .addImport(rootModulePackageName, "getMetadata") - .addType( - with(TypeSpec.classBuilder(showkaseScreenshotTestClassName)) { - superclass(ClassName(screenshotTestPackageName, testClassName)) - addAnnotation( - AnnotationSpec.builder(ShowkaseScreenshotTestWriter.RUNWITH_CLASSNAME) - .addMember("%T::class", TEST_PARAMETER_INJECTOR_CLASSNAME) - .build() - ) - addProperty(addPaparazziTestRuleProperty()) - addPreviewProvider() - addProvider( - "PaparazziShowkaseDeviceConfigProvider", - LIST.parameterizedBy(DEVICE_CONFIG_CLASS_NAME), - "deviceConfigs" - ) - addProvider( - "PaparazziShowkaseLayoutDirectionProvider", - LIST.parameterizedBy(LAYOUT_DIRECTION_CLASS_NAME), - "layoutDirections" - ) - addProvider( - "PaparazziShowkaseUIModeProvider", - LIST.parameterizedBy(UI_MODE_CLASS_NAME), - "uiModes" - ) - addTest() - build() - } - ) - - fileBuilder.build().writeTo(environment.filer, mode = XFiler.Mode.Aggregating) - } - - private fun TypeSpec.Builder.addPreviewProvider() { - addType(with(TypeSpec.objectBuilder("PaparazziShowkasePreviewProvider")) { - addSuperinterface(TEST_PARAMETER_VALUES_PROVIDER_CLASSNAME) - addModifiers(KModifier.PRIVATE) - addAnnotation(AnnotationSpec.builder(Suppress::class).addMember("\"DEPRECATION\"").build()) - addFunction( - FunSpec.builder("provideValues") - .addModifiers(KModifier.OVERRIDE) - .returns( - LIST.parameterizedBy( - PAPARAZZI_SHOWKASE_TEST_PREVIEW_CLASS_NAME - ) - ) - .addCode( - CodeBlock.builder() - .add( - "val metadata = %T.getMetadata()", - ShowkaseExtensionFunctionsWriter.SHOWKASE_OBJECT_CLASS_NAME - ) - .addLineBreak() - .add( - "val components = %N.componentList.map(::%T)", - "metadata", - COMPONENT_TEST_PREVIEW_CLASS_NAME - ) - .addLineBreak() - .add( - "val colors = %N.colorList.map(::%T)", - "metadata", - COLOR_TEST_PREVIEW_CLASS_NAME - ) - .addLineBreak() - .add( - "val typography = %N.typographyList.map(::%T)", - "metadata", - TYPOGRAPHY_TEST_PREVIEW_CLASS_NAME - ) - .addLineBreak() - .add("return components + colors + typography") - .build() - ) - .build() - ) - build() - }) - } - - private fun TypeSpec.Builder.addProvider( - name: String, - returnsTypeName: ParameterizedTypeName, - returnsMember: String - ) { - addType( - with(TypeSpec.objectBuilder(name)) { - addSuperinterface(TEST_PARAMETER_VALUES_PROVIDER_CLASSNAME) - addModifiers(KModifier.PRIVATE) - addAnnotation(AnnotationSpec.builder(Suppress::class).addMember("\"DEPRECATION\"").build()) - addFunction( - FunSpec.builder("provideValues") - .addModifiers(KModifier.OVERRIDE) - .returns(returnsTypeName) - .addCode("return $returnsMember()") - .build() - ) - build() - } - ) - } - - private fun addPaparazziTestRuleProperty() = - PropertySpec.builder( - "paparazzi", - PAPARAZZI_CLASS_NAME - ) - .addAnnotation( - AnnotationSpec.builder(ShowkaseScreenshotTestWriter.RULE_CLASSNAME) - .useSiteTarget(AnnotationSpec.UseSiteTarget.GET) - .build() - ) - .initializer("%N()", "providePaparazzi") - .build() - - @Suppress("LongMethod") - private fun TypeSpec.Builder.addTest() { - addFunction( - FunSpec.builder("test_previews") - .addAnnotation( - AnnotationSpec.builder(ShowkaseScreenshotTestWriter.JUNIT_TEST).build() - ) - .addParameter( - ParameterSpec.builder( - "elementPreview", - PAPARAZZI_SHOWKASE_TEST_PREVIEW_CLASS_NAME - ) - .addAnnotation( - AnnotationSpec.builder(TEST_PARAMETER_CLASS_NAME) - .addMember( - "valuesProvider = %N::class", - "PaparazziShowkasePreviewProvider" - ) - .build() - ) - .build() - - ) - .addParameter( - ParameterSpec.builder("config", DEVICE_CONFIG_CLASS_NAME) - .addAnnotation( - AnnotationSpec.builder(TEST_PARAMETER_CLASS_NAME) - .addMember( - "valuesProvider = %N::class", - "PaparazziShowkaseDeviceConfigProvider" - ) - .build() - ) - .build() - ) - .addParameter( - ParameterSpec.builder("direction", LAYOUT_DIRECTION_CLASS_NAME) - .addAnnotation( - AnnotationSpec.builder(TEST_PARAMETER_CLASS_NAME) - .addMember( - "valuesProvider = %N::class", - "PaparazziShowkaseLayoutDirectionProvider" - ) - .build() - ) - .build() - ) - .addParameter( - ParameterSpec.builder("uiMode", UI_MODE_CLASS_NAME) - .addAnnotation( - AnnotationSpec.builder(TEST_PARAMETER_CLASS_NAME) - .addMember( - "valuesProvider = %N::class", - "PaparazziShowkaseUIModeProvider" - ) - .build() - ) - .build() - ) - .addCode( - "%N.unsafeUpdateConfig(%N.deviceConfig.copy(softButtons = false))", - "paparazzi", - "config" - ) - .addCode("\n") - .addCode( - "takePaparazziSnapshot(%N, %N, %N, %N, elementPreview.captureType)", - "paparazzi", - "elementPreview", - "direction", - "uiMode" - ) - .build() - ) - } - - companion object { - private const val TEST_PARAMETER_INJECTOR_PACKAGE_NAME = - "com.google.testing.junit.testparameterinjector" - private val TEST_PARAMETER_INJECTOR_CLASSNAME = ClassName( - TEST_PARAMETER_INJECTOR_PACKAGE_NAME, - "TestParameterInjector" - ) - private val TEST_PARAMETER_VALUES_PROVIDER_CLASSNAME = ClassName( - TEST_PARAMETER_INJECTOR_PACKAGE_NAME, - "TestParameter.TestParameterValuesProvider" - ) - private val PAPARAZZI_CLASS_NAME = ClassName( - "app.cash.paparazzi", - "Paparazzi" - ) - private val TEST_PARAMETER_CLASS_NAME = ClassName( - TEST_PARAMETER_INJECTOR_PACKAGE_NAME, - "TestParameter" - ) - private const val PAPARAZZI_SHOWKASE_ARTIFACT_PACKAGE_NAME = - "com.airbnb.android.showkase.screenshot.testing.paparazzi" - private val PAPARAZZI_SHOWKASE_TEST_PREVIEW_CLASS_NAME = ClassName( - PAPARAZZI_SHOWKASE_ARTIFACT_PACKAGE_NAME, - "PaparazziShowkaseTestPreview" - ) - private val COMPONENT_TEST_PREVIEW_CLASS_NAME = ClassName( - PAPARAZZI_SHOWKASE_ARTIFACT_PACKAGE_NAME, - "ComponentPaparazziShowkaseTestPreview" - ) - private val COLOR_TEST_PREVIEW_CLASS_NAME = ClassName( - PAPARAZZI_SHOWKASE_ARTIFACT_PACKAGE_NAME, - "ColorPaparazziShowkaseTestPreview" - ) - private val TYPOGRAPHY_TEST_PREVIEW_CLASS_NAME = ClassName( - PAPARAZZI_SHOWKASE_ARTIFACT_PACKAGE_NAME, - "TypographyPaparazziShowkaseTestPreview" - ) - private val DEVICE_CONFIG_CLASS_NAME = ClassName( - PAPARAZZI_SHOWKASE_ARTIFACT_PACKAGE_NAME, - "PaparazziShowkaseDeviceConfig" - ) - private val UI_MODE_CLASS_NAME = ClassName( - PAPARAZZI_SHOWKASE_ARTIFACT_PACKAGE_NAME, - "PaparazziShowkaseUIMode" - ) - private val LAYOUT_DIRECTION_CLASS_NAME = ClassName( - "androidx.compose.ui.unit", - "LayoutDirection" - ) - } -} diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/RoborazziShowkaseScreenshotTestWriter.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/RoborazziShowkaseScreenshotTestWriter.kt new file mode 100644 index 000000000..5185a6e0c --- /dev/null +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/RoborazziShowkaseScreenshotTestWriter.kt @@ -0,0 +1,171 @@ +package com.airbnb.android.showkase.processor.writer + +import com.google.devtools.ksp.processing.CodeGenerator +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.ksp.writeTo + +/** + * Generates the Roborazzi-backed screenshot test class for classes annotated with + * `@ShowkaseScreenshot` that implement [RoborazziShowkaseScreenshotTest]. + * + * The generated test: + * - runs under [RobolectricTestRunner] with `@GraphicsMode(NATIVE)` and `@Config(sdk = 33)` + * (Robolectric requires these at class level; SDK cannot change at runtime), + * - iterates every Showkase preview (components + colors + typography) inside a single `@Test`, + * collecting per-preview failures so all diffs surface in one run, + * - reads the device qualifier from the user's `companion object` at runtime via + * `RuntimeEnvironment.setQualifiers(...)`, so users can override qualifiers without forking the + * generator, + * - writes goldens to `src/test/snapshots/roborazzi/.png` and compares against them. + */ +class RoborazziShowkaseScreenshotTestWriter(private val codeGenerator: CodeGenerator) { + + internal fun generateScreenshotTests( + screenshotTestPackageName: String, + rootModulePackageName: String, + testClassName: String, + ) { + val implClassName = "${testClassName}Impl" + val userTestClass = ClassName(screenshotTestPackageName, testClassName) + val fileBuilder = getFileBuilder(screenshotTestPackageName, implClassName) + + fileBuilder + .addImport(rootModulePackageName, "getMetadata") + .addType( + TypeSpec.classBuilder(implClassName) + .addAnnotation(runWithAndroidJUnit4()) + .addAnnotation(graphicsModeNative()) + .addAnnotation(configSdk()) + .addFunction(testAllPreviewsFunction(userTestClass)) + .build(), + ) + + fileBuilder.build().writeTo(codeGenerator, aggregating = true) + } + + private fun runWithAndroidJUnit4(): AnnotationSpec = + AnnotationSpec.builder(ShowkaseScreenshotTestWriter.RUNWITH_CLASSNAME) + .addMember("%T::class", ROBOLECTRIC_TEST_RUNNER) + .build() + + private fun graphicsModeNative(): AnnotationSpec = + AnnotationSpec.builder(GRAPHICS_MODE_CLASS_NAME) + .addMember("%T.Mode.NATIVE", GRAPHICS_MODE_CLASS_NAME) + .build() + + private fun configSdk(): AnnotationSpec = + AnnotationSpec.builder(CONFIG_CLASS_NAME) + .addMember("sdk = [33]") + .build() + + /** + * Emits a single `@Test fun test_all_previews()` that iterates every Showkase preview and + * snapshots each. We can't use [ParameterizedRobolectricTestRunner] because its `@Parameters` + * factory runs before Robolectric's sandbox is initialized, and `Showkase.getMetadata()`'s + * reflective lookup of the generated `*RootModuleCodegen` class returns empty in that state. + * + * Per-preview failure isolation is sacrificed for reliability — a diff on one preview still + * lets us continue to the next via `captureRoboImage`'s default behaviour (it throws on diff, + * but we wrap each call in `runCatching` so we collect all failures before failing the test). + */ + private fun testAllPreviewsFunction(userTestClass: ClassName): FunSpec = + FunSpec.builder("test_all_previews") + .addAnnotation(ShowkaseScreenshotTestWriter.JUNIT_TEST) + .addCode( + CodeBlock.builder() + .addStatement( + "%T.setQualifiers(%T.qualifiers())", + RUNTIME_ENVIRONMENT_CLASS_NAME, + userTestClass, + ) + .addStatement( + "val metadata = %T.getMetadata()", + ShowkaseExtensionFunctionsWriter.SHOWKASE_OBJECT_CLASS_NAME, + ) + .addStatement( + "val previews = mutableListOf<%T>()", + ROBORAZZI_TEST_PREVIEW_CLASS_NAME, + ) + .addStatement( + "metadata.componentList.mapTo(previews, ::%T)", + COMPONENT_TEST_PREVIEW_CLASS_NAME, + ) + .addStatement( + "metadata.colorList.mapTo(previews, ::%T)", + COLOR_TEST_PREVIEW_CLASS_NAME, + ) + .addStatement( + "metadata.typographyList.mapTo(previews, ::%T)", + TYPOGRAPHY_TEST_PREVIEW_CLASS_NAME, + ) + .addStatement("val outputDir = %S", "src/test/snapshots/roborazzi") + .addStatement("val options = %T.roborazziOptions()", userTestClass) + .addStatement("val failures = mutableListOf()") + .beginControlFlow("previews.forEach { preview ->") + .beginControlFlow("runCatching") + .addStatement( + "%T(filePath = %P, roborazziOptions = options) { preview.Content() }", + CAPTURE_ROBO_IMAGE_CLASS_NAME, + "\${outputDir}/\${preview.id}.png", + ) + .endControlFlow() + .beginControlFlow(".onFailure { e ->") + .addStatement("failures += %P", "\${preview.id}: \${e.message}") + .endControlFlow() + .endControlFlow() + .beginControlFlow("if (failures.isNotEmpty())") + .addStatement( + "error(\"Roborazzi screenshot failures (\${failures.size}):\\n\" + " + + "failures.joinToString(\"\\n\"))" + ) + .endControlFlow() + .build(), + ) + .build() + + companion object { + private const val ROBOLECTRIC = "org.robolectric" + private val ROBOLECTRIC_TEST_RUNNER = ClassName( + ROBOLECTRIC, + "RobolectricTestRunner", + ) + private val GRAPHICS_MODE_CLASS_NAME = ClassName( + "$ROBOLECTRIC.annotation", + "GraphicsMode", + ) + private val CONFIG_CLASS_NAME = ClassName( + "$ROBOLECTRIC.annotation", + "Config", + ) + private val RUNTIME_ENVIRONMENT_CLASS_NAME = ClassName( + ROBOLECTRIC, + "RuntimeEnvironment", + ) + + private const val ROBORAZZI = "com.github.takahirom.roborazzi" + private val CAPTURE_ROBO_IMAGE_CLASS_NAME = ClassName(ROBORAZZI, "captureRoboImage") + + private const val ROBORAZZI_SHOWKASE_PACKAGE = + "com.airbnb.android.showkase.screenshot.testing.roborazzi" + private val ROBORAZZI_TEST_PREVIEW_CLASS_NAME = ClassName( + ROBORAZZI_SHOWKASE_PACKAGE, + "RoborazziShowkaseTestPreview", + ) + private val COMPONENT_TEST_PREVIEW_CLASS_NAME = ClassName( + ROBORAZZI_SHOWKASE_PACKAGE, + "ComponentRoborazziShowkaseTestPreview", + ) + private val COLOR_TEST_PREVIEW_CLASS_NAME = ClassName( + ROBORAZZI_SHOWKASE_PACKAGE, + "ColorRoborazziShowkaseTestPreview", + ) + private val TYPOGRAPHY_TEST_PREVIEW_CLASS_NAME = ClassName( + ROBORAZZI_SHOWKASE_PACKAGE, + "TypographyRoborazziShowkaseTestPreview", + ) + } +} diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserPropertyWriter.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserPropertyWriter.kt index 942c45803..70ac152b8 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserPropertyWriter.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserPropertyWriter.kt @@ -1,20 +1,20 @@ package com.airbnb.android.showkase.processor.writer -import androidx.room.compiler.processing.XFiler -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.addOriginatingElement -import androidx.room.compiler.processing.writeTo import com.airbnb.android.showkase.processor.ShowkaseGeneratedMetadata import com.airbnb.android.showkase.processor.ShowkaseGeneratedMetadataType import com.airbnb.android.showkase.processor.models.ShowkaseMetadata +import com.airbnb.android.showkase.processor.utils.containingFileOrNull +import com.google.devtools.ksp.processing.CodeGenerator import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.asTypeName +import com.squareup.kotlinpoet.ksp.addOriginatingKSFile +import com.squareup.kotlinpoet.ksp.writeTo -class ShowkaseBrowserPropertyWriter(private val environment: XProcessingEnv) { +class ShowkaseBrowserPropertyWriter(private val codeGenerator: CodeGenerator) { @Suppress("LongMethod") internal fun generateMetadataPropertyFiles( componentMetadata: Set, @@ -137,7 +137,9 @@ class ShowkaseBrowserPropertyWriter(private val environment: XProcessingEnv) { } .build() ) - .addOriginatingElement(showkaseMetadata.element) + .apply { + showkaseMetadata.element.containingFileOrNull()?.let { addOriginatingKSFile(it) } + } .build() private fun getPropertyForComponentWithoutParameter( @@ -156,7 +158,9 @@ class ShowkaseBrowserPropertyWriter(private val environment: XProcessingEnv) { } .build() ) - .addOriginatingElement(showkaseMetadata.element) + .apply { + showkaseMetadata.element.containingFileOrNull()?.let { addOriginatingKSFile(it) } + } .build() return property } @@ -178,7 +182,9 @@ class ShowkaseBrowserPropertyWriter(private val environment: XProcessingEnv) { .build() ) } - .addOriginatingElement(showkaseMetadata.element) + .apply { + showkaseMetadata.element.containingFileOrNull()?.let { addOriginatingKSFile(it) } + } .build() } @@ -186,8 +192,7 @@ class ShowkaseBrowserPropertyWriter(private val environment: XProcessingEnv) { propertySpec: PropertySpec, ) { addProperty(propertySpec) - build() - .writeTo(environment.filer, mode = XFiler.Mode.Isolating) + build().writeTo(codeGenerator, aggregating = false) } private fun CodeBlock.Builder.addPreviewProviderComponent(withParameterMetadata: ShowkaseMetadata.Component) { diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserWriter.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserWriter.kt index 746395131..f89eb8d6c 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserWriter.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserWriter.kt @@ -1,15 +1,15 @@ package com.airbnb.android.showkase.processor.writer -import androidx.room.compiler.processing.XElement -import androidx.room.compiler.processing.XFiler -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.addOriginatingElement -import androidx.room.compiler.processing.isTypeElement -import androidx.room.compiler.processing.writeTo import com.airbnb.android.showkase.annotation.ShowkaseMultiPreviewCodegenMetadata import com.airbnb.android.showkase.annotation.ShowkaseRootCodegen import com.airbnb.android.showkase.processor.ShowkaseGeneratedMetadata import com.airbnb.android.showkase.processor.ShowkaseProcessor +import com.airbnb.android.showkase.processor.utils.getAsInt +import com.airbnb.android.showkase.processor.utils.getAsString +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.symbol.ClassKind +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock @@ -19,9 +19,11 @@ import com.squareup.kotlinpoet.LIST import com.squareup.kotlinpoet.MemberName import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.ksp.addOriginatingKSFile +import com.squareup.kotlinpoet.ksp.writeTo import java.util.Locale -internal class ShowkaseBrowserWriter(private val environment: XProcessingEnv) { +internal class ShowkaseBrowserWriter(private val codeGenerator: CodeGenerator) { @Suppress("LongMethod", "LongParameterList") internal fun generateShowkaseBrowserFile( allShowkaseBrowserProperties: ShowkaseBrowserProperties, @@ -46,7 +48,7 @@ internal class ShowkaseBrowserWriter(private val environment: XProcessingEnv) { ) writeFile( - environment, + codeGenerator, fileBuilder, SHOWKASE_PROVIDER_CLASS_NAME, showkaseComponentsListClassName, @@ -160,16 +162,19 @@ internal class ShowkaseBrowserWriter(private val environment: XProcessingEnv) { .build() // This is to aggregate metadata for the custom annotation annotated with Preview - internal fun writeCustomAnnotationElementToMetadata(element: XElement) { - if (!element.isTypeElement()) return - if (element.isAnnotationClass() && element.qualifiedName == ShowkaseProcessor.PREVIEW_CLASS_NAME) return - - val moduleName = "Showkase_${element.qualifiedName.replace(".", "_")}" + internal fun writeCustomAnnotationElementToMetadata(element: KSAnnotated) { + if (element !is KSClassDeclaration) return + val qualifiedName = element.qualifiedName?.asString() + val isPreviewAnnotation = element.classKind == ClassKind.ANNOTATION_CLASS && + qualifiedName == ShowkaseProcessor.PREVIEW_CLASS_NAME + if (qualifiedName == null || isPreviewAnnotation) return + + val moduleName = "Showkase_${qualifiedName.replace(".", "_")}" val generatedClassName = "ShowkaseMetadata_${moduleName.lowercase(Locale.getDefault())}" val previewAnnotations = - element.getAllAnnotations().filter { it.name == ShowkaseProcessor.PREVIEW_SIMPLE_NAME } + element.annotations.filter { it.shortName.asString() == ShowkaseProcessor.PREVIEW_SIMPLE_NAME }.toList() val fileBuilder = FileSpec.builder( ShowkaseProcessor.CODEGEN_PACKAGE_NAME, @@ -177,27 +182,27 @@ internal class ShowkaseBrowserWriter(private val environment: XProcessingEnv) { ) val functions = previewAnnotations.mapIndexed { index, xAnnotation -> - FunSpec.builder("${xAnnotation.name}_$index") + FunSpec.builder("${xAnnotation.shortName.asString()}_$index") .addAnnotation( AnnotationSpec .builder(ShowkaseMultiPreviewCodegenMetadata::class) .addMember("previewName = %S", xAnnotation.getAsString("name")) .addMember("previewGroup = %S", xAnnotation.getAsString("group")) - .addMember("supportTypeQualifiedName = %S", element.qualifiedName) + .addMember("supportTypeQualifiedName = %S", qualifiedName) .addMember("showkaseWidth = %L", xAnnotation.getAsInt("widthDp")) .addMember("showkaseHeight = %L", xAnnotation.getAsInt("heightDp")) - .addMember("packageName = %S", element.packageName) + .addMember("packageName = %S", element.packageName.asString()) .build() ).build() } fileBuilder.addType( with(TypeSpec.classBuilder(generatedClassName).addFunctions(functions)) { - addOriginatingElement(element) + element.containingFile?.let { addOriginatingKSFile(it) } build() } ).addFileComment("This is an auto-generated file. Please do not edit/modify this file.") - fileBuilder.build().writeTo(environment.filer, mode = XFiler.Mode.Aggregating) + fileBuilder.build().writeTo(codeGenerator, aggregating = true) } companion object { diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseCodegenMetadataWriter.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseCodegenMetadataWriter.kt index c0be01569..940805a46 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseCodegenMetadataWriter.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseCodegenMetadataWriter.kt @@ -1,20 +1,20 @@ package com.airbnb.android.showkase.processor.writer -import androidx.room.compiler.processing.XFiler -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.addOriginatingElement -import androidx.room.compiler.processing.writeTo import com.airbnb.android.showkase.annotation.ShowkaseCodegenMetadata import com.airbnb.android.showkase.processor.ShowkaseProcessor.Companion.CODEGEN_PACKAGE_NAME import com.airbnb.android.showkase.processor.models.ShowkaseMetadata import com.airbnb.android.showkase.processor.models.ShowkaseMetadataType +import com.airbnb.android.showkase.processor.utils.containingFileOrNull +import com.google.devtools.ksp.processing.CodeGenerator import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.ksp.addOriginatingKSFile +import com.squareup.kotlinpoet.ksp.writeTo import java.util.Locale -internal class ShowkaseCodegenMetadataWriter(private val environment: XProcessingEnv) { +internal class ShowkaseCodegenMetadataWriter(private val codeGenerator: CodeGenerator) { internal fun generateShowkaseCodegenFunctions( showkaseMetadataSet: Set, @@ -64,12 +64,14 @@ internal class ShowkaseCodegenMetadataWriter(private val environment: XProcessin fileBuilder.addType( with(autogenClass) { - showkaseMetadataSet.forEach { addOriginatingElement(it.element) } + showkaseMetadataSet.forEach { meta -> + meta.element.containingFileOrNull()?.let { addOriginatingKSFile(it) } + } build() } ) - fileBuilder.build().writeTo(environment.filer, mode = XFiler.Mode.Aggregating) + fileBuilder.build().writeTo(codeGenerator, aggregating = true) } private fun createShowkaseCodegenMetadata(showkaseMetadata: ShowkaseMetadata): AnnotationSpec.Builder = @@ -84,6 +86,7 @@ internal class ShowkaseCodegenMetadataWriter(private val environment: XProcessin .addMember("showkaseKDoc = %S", showkaseMetadata.showkaseKDoc) .addMember("generatedPropertyName = %S", generatePropertyNameFromMetadata(showkaseMetadata)) + @Suppress("NestedBlockDepth") private fun addMetadataTypeSpecificProperties( showkaseMetadata: ShowkaseMetadata, annotation: AnnotationSpec.Builder @@ -109,6 +112,18 @@ internal class ShowkaseCodegenMetadataWriter(private val environment: XProcessin } addStringArrayMember(ShowkaseCodegenMetadata::tags.name, showkaseMetadata.tags) addStringArrayMember(ShowkaseCodegenMetadata::extraMetadata.name, showkaseMetadata.extraMetadata) + if (showkaseMetadata.isDialog) { + addMember("isDialog = %L", true) + if (showkaseMetadata.dialogButtonText.isNotEmpty()) { + addMember("dialogButtonText = %S", showkaseMetadata.dialogButtonText) + } + if (showkaseMetadata.dialogHideButtonText.isNotEmpty()) { + addMember( + "dialogHideButtonText = %S", + showkaseMetadata.dialogHideButtonText + ) + } + } } } diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseExtensionFunctionsWriter.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseExtensionFunctionsWriter.kt index bf7c9029c..a777cc141 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseExtensionFunctionsWriter.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseExtensionFunctionsWriter.kt @@ -1,25 +1,24 @@ package com.airbnb.android.showkase.processor.writer -import androidx.room.compiler.processing.XFiler -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.XTypeElement -import androidx.room.compiler.processing.addOriginatingElement -import androidx.room.compiler.processing.writeTo import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.SHOWKASE_MODELS_PACKAGE_NAME import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.SHOWKASE_PROVIDER_CLASS_NAME +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.symbol.KSClassDeclaration import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.ksp.addOriginatingKSFile +import com.squareup.kotlinpoet.ksp.writeTo internal class ShowkaseExtensionFunctionsWriter( - private val environment: XProcessingEnv + private val codeGenerator: CodeGenerator ) { internal fun generateShowkaseExtensionFunctions( rootModulePackageName: String, rootModuleClassName: String, - rootElement: XTypeElement + rootElement: KSClassDeclaration ) { - getFileBuilder( + val fileBuilder = getFileBuilder( rootModulePackageName, "${rootModuleClassName}$SHOWKASE_METHODS_SUFFIX" ) @@ -36,14 +35,18 @@ internal class ShowkaseExtensionFunctionsWriter( "$rootModulePackageName.$rootModuleClassName" ) ) - .build() - .writeTo(environment.filer, mode = XFiler.Mode.Aggregating) + + rootElement.containingFile?.let { fileBuilder.addFileComment("").build() } + val built = fileBuilder.build() + // Originating files for FunSpecs are picked up via the FileSpec automatically when + // FunSpec.Builder.addOriginatingKSFile is used. + built.writeTo(codeGenerator, aggregating = true) } private fun generateIntentFunction( rootModulePackageName: String, rootModuleClassName: String, - rootElement: XTypeElement, + rootElement: KSClassDeclaration, ) = FunSpec.builder(INTENT_FUNCTION_NAME).apply { addParameter( CONTEXT_PARAMETER_NAME, CONTEXT_CLASS_NAME @@ -72,12 +75,12 @@ internal class ShowkaseExtensionFunctionsWriter( .unindent() .build() ) - addOriginatingElement(rootElement) + rootElement.containingFile?.let { addOriginatingKSFile(it) } } .build() private fun generateMetadataFunction( - rootElement: XTypeElement, + rootElement: KSClassDeclaration, classKey: String ) = FunSpec.builder(METADATA_FUNCTION_NAME).apply { val errorMessage = "The class wasn't generated correctly. Make sure that you have setup " + @@ -109,7 +112,7 @@ internal class ShowkaseExtensionFunctionsWriter( .unindent() .build() ) - addOriginatingElement(rootElement) + rootElement.containingFile?.let { addOriginatingKSFile(it) } } .build() diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseScreenshotTestWriter.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseScreenshotTestWriter.kt index 6ba84d7b0..30b7826a5 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseScreenshotTestWriter.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseScreenshotTestWriter.kt @@ -1,20 +1,19 @@ package com.airbnb.android.showkase.processor.writer -import androidx.room.compiler.processing.XFiler -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.writeTo import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.COLOR_PROPERTY_NAME import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.COMPONENT_PROPERTY_NAME import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.TYPOGRAPHY_PROPERTY_NAME import com.airbnb.android.showkase.processor.writer.ShowkaseExtensionFunctionsWriter.Companion.SHOWKASE_OBJECT_CLASS_NAME +import com.google.devtools.ksp.processing.CodeGenerator import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.ksp.writeTo -internal class ShowkaseScreenshotTestWriter(private val environment: XProcessingEnv) { +internal class ShowkaseScreenshotTestWriter(private val codeGenerator: CodeGenerator) { @Suppress("LongParameterList") internal fun generateScreenshotTests( componentsSize: Int, @@ -45,7 +44,7 @@ internal class ShowkaseScreenshotTestWriter(private val environment: XProcessing } ) - fileBuilder.build().writeTo(environment.filer, mode = XFiler.Mode.Aggregating) + fileBuilder.build().writeTo(codeGenerator, aggregating = true) } private fun addComposeTestRuleProperty() = diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/WriterUtils.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/WriterUtils.kt index f370b481c..df53b3511 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/WriterUtils.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/WriterUtils.kt @@ -1,12 +1,10 @@ package com.airbnb.android.showkase.processor.writer -import androidx.room.compiler.processing.XFiler -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.addOriginatingElement -import androidx.room.compiler.processing.writeTo import com.airbnb.android.showkase.annotation.ScreenshotConfig import com.airbnb.android.showkase.processor.exceptions.ShowkaseProcessorException import com.airbnb.android.showkase.processor.models.ShowkaseMetadata +import com.airbnb.android.showkase.processor.utils.containingFileOrNull +import com.google.devtools.ksp.processing.CodeGenerator import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock @@ -19,6 +17,8 @@ import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.ksp.addOriginatingKSFile +import com.squareup.kotlinpoet.ksp.writeTo val SPACE_REGEX = "\\s".toRegex() @@ -49,7 +49,7 @@ internal fun getShowkaseProviderInterfaceFunction( @Suppress("LongParameterList") internal fun writeFile( - processingEnv: XProcessingEnv, + codeGenerator: CodeGenerator, fileBuilder: FileSpec.Builder, superInterfaceClassName: ClassName, showkaseComponentsListClassName: String, @@ -67,12 +67,14 @@ internal fun writeFile( addFunction(componentInterfaceFunction) addFunction(colorInterfaceFunction) addFunction(typographyInterfaceFunction) - allShowkaseBrowserProperties.zip().forEach { addOriginatingElement(it.element) } + allShowkaseBrowserProperties.zip().forEach { meta -> + meta.element.containingFileOrNull()?.let { addOriginatingKSFile(it) } + } build() } ) - fileBuilder.build().writeTo(processingEnv.filer, mode = XFiler.Mode.Aggregating) + fileBuilder.build().writeTo(codeGenerator, aggregating = true) } internal fun ClassName.listInitializerCodeBlock(): CodeBlock.Builder { @@ -99,6 +101,7 @@ internal fun ClassName.getCodegenMetadataParameterizedList() = List::class .asClassName() .parameterizedBy(this) +@Suppress("LongMethod") internal fun CodeBlock.Builder.addShowkaseBrowserComponent( showkaseMetadata: ShowkaseMetadata.Component, isPreviewParameter: Boolean = false @@ -140,6 +143,15 @@ internal fun CodeBlock.Builder.addShowkaseBrowserComponent( addStringList("tags", showkaseMetadata.tags) addStringList("extraMetadata", showkaseMetadata.extraMetadata) add("\nscreenshotConfig = %L,", screenshotConfigCodeBlock(showkaseMetadata.screenshotConfig)) + if (showkaseMetadata.isDialog) { + add("\nisDialog = true,") + if (showkaseMetadata.dialogButtonText.isNotEmpty()) { + add("\ndialogButtonText = %S,", showkaseMetadata.dialogButtonText) + } + if (showkaseMetadata.dialogHideButtonText.isNotEmpty()) { + add("\ndialogHideButtonText = %S,", showkaseMetadata.dialogHideButtonText) + } + } add( composePreviewFunctionLambdaCodeBlock( showkaseMetadata.packageName, diff --git a/showkase-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/showkase-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor deleted file mode 100644 index 196bc20c0..000000000 --- a/showkase-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ /dev/null @@ -1 +0,0 @@ -com.airbnb.android.showkase.processor.ShowkaseProcessor \ No newline at end of file diff --git a/showkase-processor/src/test/kotlin/com/airbnb/android/showkase/processor/models/ShowkaseMetadataTest.kt b/showkase-processor/src/test/kotlin/com/airbnb/android/showkase/processor/models/ShowkaseMetadataTest.kt index 5176b476e..be2038478 100644 --- a/showkase-processor/src/test/kotlin/com/airbnb/android/showkase/processor/models/ShowkaseMetadataTest.kt +++ b/showkase-processor/src/test/kotlin/com/airbnb/android/showkase/processor/models/ShowkaseMetadataTest.kt @@ -1,9 +1,12 @@ package com.airbnb.android.showkase.processor.models -import androidx.room.compiler.processing.XFieldElement -import androidx.room.compiler.processing.XMethodElement -import androidx.room.compiler.processing.util.Source -import androidx.room.compiler.processing.util.runProcessorTest +import com.airbnb.android.showkase.processor.util.runKspProcessorTest +import com.google.devtools.ksp.getDeclaredFunctions +import com.google.devtools.ksp.getDeclaredProperties +import com.google.devtools.ksp.symbol.FunctionKind +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.tschuchort.compiletesting.SourceFile import org.junit.Test import strikt.api.expectThat import strikt.assertions.isA @@ -15,7 +18,7 @@ class ShowkaseMetadataTest { @Test fun isTopLevelFunction() { - val libSource = Source.kotlin( + val libSource = SourceFile.kotlin( "lib.kt", """ @com.airbnb.android.showkase.processor.models.MyAnnotation @@ -26,25 +29,24 @@ class ShowkaseMetadataTest { } """.trimIndent() ) - runProcessorTest(listOf(libSource)) { invocation -> - val barClass = invocation.processingEnv.requireTypeElement("Bar") + runKspProcessorTest(listOf(libSource)) { resolver -> + val barClass = resolver.getClassDeclarationByName(resolver.getKSNameFromString("Bar"))!! + val enclosedFn = barClass.getDeclaredFunctions() + .single { it.simpleName.asString() == "enclosedFoo" } - expectThat(barClass.getDeclaredMethods() - .single()) - .get { isTopLevel(enclosingElement) } - .isFalse() + expectThat(enclosedFn.functionKind == FunctionKind.TOP_LEVEL).isFalse() - expectThat(invocation.roundEnv.getElementsAnnotatedWith(MyAnnotation::class)) + expectThat(resolver.getSymbolsWithAnnotation(MyAnnotation::class.qualifiedName!!).toList()) .single() - .isA() - .get { isTopLevel(enclosingElement) } + .isA() + .get { functionKind == FunctionKind.TOP_LEVEL } .isTrue() } } @Test fun isTopLevelFunctionProperty() { - val libSource = Source.kotlin( + val libSource = SourceFile.kotlin( "lib.kt", """ @com.airbnb.android.showkase.processor.models.MyAnnotation @@ -55,26 +57,18 @@ class ShowkaseMetadataTest { } """.trimIndent() ) - runProcessorTest(listOf(libSource)) { invocation -> - val barClass = invocation.processingEnv.requireTypeElement("Bar") + runKspProcessorTest(listOf(libSource)) { resolver -> + val barClass = resolver.getClassDeclarationByName(resolver.getKSNameFromString("Bar"))!! + val enclosedProp = barClass.getDeclaredProperties() + .single { it.simpleName.asString() == "enclosedFoo" } - expectThat(barClass.getDeclaredMethods().single()) - .get { isTopLevel(enclosingElement) } - .isFalse() + expectThat(enclosedProp.parentDeclaration == null).isFalse() - if (invocation.isKsp) { - expectThat(invocation.roundEnv.getElementsAnnotatedWith(MyAnnotation::class)) - .single() - .isA() - .get { isTopLevel(enclosingElement) } - .isTrue() - } else { - expectThat(invocation.roundEnv.getElementsAnnotatedWith(MyAnnotation::class)) - .single() - .isA() - .get { isTopLevel(enclosingElement) } - .isTrue() - } + expectThat(resolver.getSymbolsWithAnnotation(MyAnnotation::class.qualifiedName!!).toList()) + .single() + .isA() + .get { parentDeclaration == null } + .isTrue() } } } diff --git a/showkase-processor/src/test/kotlin/com/airbnb/android/showkase/processor/util/KspProcessorRunner.kt b/showkase-processor/src/test/kotlin/com/airbnb/android/showkase/processor/util/KspProcessorRunner.kt new file mode 100644 index 000000000..357626e30 --- /dev/null +++ b/showkase-processor/src/test/kotlin/com/airbnb/android/showkase/processor/util/KspProcessorRunner.kt @@ -0,0 +1,41 @@ +package com.airbnb.android.showkase.processor.util + +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider +import com.google.devtools.ksp.symbol.KSAnnotated +import com.tschuchort.compiletesting.KotlinCompilation +import com.tschuchort.compiletesting.SourceFile +import com.tschuchort.compiletesting.configureKsp +import com.tschuchort.compiletesting.symbolProcessorProviders +import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi + +@OptIn(ExperimentalCompilerApi::class) +internal fun runKspProcessorTest( + sources: List, + block: (Resolver) -> Unit, +) { + val provider = object : SymbolProcessorProvider { + override fun create(env: SymbolProcessorEnvironment): SymbolProcessor = + object : SymbolProcessor { + override fun process(resolver: Resolver): List { + block(resolver) + return emptyList() + } + } + } + + val compilation = KotlinCompilation().apply { + this.sources = sources + inheritClassPath = true + configureKsp { + symbolProcessorProviders += provider + } + } + + val result = compilation.compile() + check(result.exitCode == KotlinCompilation.ExitCode.OK) { + "Compilation failed:\n${result.messages}" + } +} diff --git a/showkase-screenshot-testing-paparazzi-sample/.gitignore b/showkase-screenshot-testing-paparazzi-sample/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/showkase-screenshot-testing-paparazzi-sample/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/showkase-screenshot-testing-paparazzi-sample/build.gradle b/showkase-screenshot-testing-paparazzi-sample/build.gradle deleted file mode 100644 index e8830afb1..000000000 --- a/showkase-screenshot-testing-paparazzi-sample/build.gradle +++ /dev/null @@ -1,159 +0,0 @@ -buildscript { - repositories { - mavenCentral() - google() - } -} - -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'app.cash.paparazzi' version "2.0.0-alpha02" - id 'org.jetbrains.kotlin.plugin.compose' -} - -if (project.hasProperty('useKsp')) { - apply plugin: 'com.google.devtools.ksp' -} else { - apply plugin: 'kotlin-kapt' - kapt { - correctErrorTypes = true - } -} - -android { - namespace "com.airbnb.android.showkase.screenshot.testing.paparazzi" - // Added to avoid this error - - // Execution failed for task ':showkase-processor-testing:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade - // > More than one file was found with OS independent path 'META-INF/gradle/incremental.annotation.processors' - packagingOptions { - exclude 'META-INF/gradle/incremental.annotation.processors' - exclude("META-INF/*.kotlin_module") - } - defaultConfig { - minSdkVersion 21 - compileSdk 36 - targetSdkVersion 33 - // The following argument makes the Android Test Orchestrator run its - // "pm clear" command after each test invocation. This command ensures - // that the app's state is completely cleared between tests. - testInstrumentationRunnerArguments clearPackageData: 'true' - } - - buildFeatures { - compose true - } - // Added to avoid this error - - // Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction - // > 2 files found with path 'META-INF/AL2.0' from inputs: - packagingOptions { - exclude 'META-INF/AL2.0' - exclude 'META-INF/LGPL2.1' - } -} - -// https://github.com/cashapp/paparazzi/issues/409 -tasks.withType(Test).configureEach { task -> - task.jvmArgs += [ - "--add-opens=java.base/java.lang=ALL-UNNAMED", - "--add-opens=java.base/java.lang.invoke=ALL-UNNAMED", - "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED", - ] -} - -kotlin { - jvmToolchain(17) -} - -afterEvaluate { - /** - * KSP does not currently register kotlin generated sources. - * https://github.com/google/ksp/issues/37 - */ - if (project.extensions.findByType(com.android.build.gradle.LibraryExtension.class) != null) { - project.android.libraryVariants.all { variant -> - def variantName = variant.name - def outputFolder = new File("build/generated/ksp/$variantName/kotlin") - variant.addJavaSourceFoldersToModel(outputFolder) - android.sourceSets.getAt(variantName).java { - srcDir(outputFolder) - } - - // Register the generated unit test sources as well - // Note, there is a nuanced different between the name of the directory on disk and the source set name - // in AGP. - // eg "debugUnitTest" - def testDirectoryName = "${variantName}UnitTest" - // eg "testDebug" - def testSourceSetName = "test${variantName.capitalize()}" - - // Not every module will have test sources, so finding it is optional. - android.sourceSets.findByName(testSourceSetName)?.kotlin { - def testOutputFolder = new File("build/generated/ksp/$testDirectoryName/kotlin") - srcDir(testOutputFolder) - } - } - } else if (project.extensions.findByType(com.android.build.gradle.AbstractAppExtension.class) != null) { - project.android.applicationVariants.all { variant -> - def variantName = variant.name - def outputFolder = new File("build/generated/ksp/$variantName/kotlin") - variant.addJavaSourceFoldersToModel(outputFolder) - android.sourceSets.getAt(variantName).java { - srcDir(outputFolder) - } - - // Register the generated unit test sources as well - // Note, there is a nuanced different between the name of the directory on disk and the source set name - // in AGP. - // eg "debugUnitTest" - def testDirectoryName = "${variantName}UnitTest" - // eg "testDebug" - def testSourceSetName = "test${variantName.capitalize()}" - - // Not every module will have test sources, so finding it is optional. - android.sourceSets.findByName(testSourceSetName)?.kotlin { - def testOutputFolder = new File("build/generated/ksp/$testDirectoryName/kotlin") - srcDir(testOutputFolder) - } - } - } -} - -dependencies { - // Showkase - implementation project(':showkase') - if (project.hasProperty('useKsp')) { - ksp project(':showkase-processor') - kspAndroidTest project(':showkase-processor') - kspTest project(':showkase-processor') - } else { - kapt project(':showkase-processor') - kaptAndroidTest project(':showkase-processor') - kaptTest project(':showkase-processor') - } - api project(':showkase-screenshot-testing') - - // Compose - implementation deps.compose.activityCompose - implementation deps.compose.composeRuntime - implementation deps.compose.constraintLayout - implementation deps.compose.core - implementation deps.compose.foundation - implementation deps.compose.tooling - implementation deps.compose.layout - implementation deps.compose.material - implementation deps.compose.savedInstanceState - implementation deps.compose.uiLiveData - - // Image loading - implementation deps.imageLoading.picasso - - // Testing - testImplementation deps.test.junit - testImplementation deps.test.junitImplementation - implementation deps.test.testParameterInjector - testImplementation deps.compose.uiTest - testImplementation project(':showkase-screenshot-testing-paparazzi') -} \ No newline at end of file diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/AndroidManifest.xml b/showkase-screenshot-testing-paparazzi-sample/src/main/AndroidManifest.xml deleted file mode 100644 index a5918e68a..000000000 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/MyPaparazziShowkaseScreenshotTest.kt b/showkase-screenshot-testing-paparazzi-sample/src/test/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/MyPaparazziShowkaseScreenshotTest.kt deleted file mode 100644 index 4c03efabd..000000000 --- a/showkase-screenshot-testing-paparazzi-sample/src/test/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/MyPaparazziShowkaseScreenshotTest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample - -import com.airbnb.android.showkase.annotation.ShowkaseScreenshot -import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest - -@ShowkaseScreenshot(rootShowkaseClass = PaparazziSampleRootModule::class) -abstract class MyPaparazziShowkaseScreenshotTest : PaparazziShowkaseScreenshotTest { - companion object : PaparazziShowkaseScreenshotTest.CompanionObject -} diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/PaparazziSampleScreenshotTest.kt b/showkase-screenshot-testing-paparazzi-sample/src/test/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/PaparazziSampleScreenshotTest.kt deleted file mode 100644 index f57dcc8ed..000000000 --- a/showkase-screenshot-testing-paparazzi-sample/src/test/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/PaparazziSampleScreenshotTest.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed 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 - * - * https://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. - */ - -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample - -import androidx.activity.OnBackPressedDispatcher -import androidx.activity.OnBackPressedDispatcherOwner -import androidx.activity.compose.LocalOnBackPressedDispatcherOwner -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.BasicText -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle -import com.airbnb.android.showkase.models.ShowkaseBrowserComponent -import com.google.testing.junit.testparameterinjector.TestParameter -import org.junit.Rule -import app.cash.paparazzi.DeviceConfig -import app.cash.paparazzi.Paparazzi -import com.airbnb.android.showkase.models.Showkase -import com.airbnb.android.showkase.models.ShowkaseBrowserColor -import com.airbnb.android.showkase.models.ShowkaseBrowserTypography -import com.airbnb.android.showkase.ui.padding4x -import java.util.* - -/** - * Credit to Alex Vanyo for creating this sample in the Now In Android app by Google. - * PR here - https://github.com/android/nowinandroid/pull/101. Modified the test from that PR to - * my own needs for this sample. - * - * - * The "@RunWith" & "@Test" annotations have been commented out so these tests won't run. - * This is because I don't want to run these tests but instead run the test auto generated by the - * showkase-screenshot-testing-paparazzi artifact. - * - * I've kept this file for reference in case you want to write a custom Paparazzi + Showkase test - * for your use case. For what it's worth, the showkase-screenshot-testing-paparazzi artifact - * auto-generates an identical class for you so that you don't have to manually write this. - */ -// @RunWith(TestParameterInjector::class) -class PaparazziSampleScreenshotTest { - - object PreviewProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List { - val metadata = Showkase.getMetadata() - val components = metadata.componentList.map(::ComponentTestPreview) - val colors = metadata.colorList.map(::ColorTestPreview) - val typography = metadata.typographyList.map(::TypographyTestPreview) - - return components + colors + typography - } - } - - enum class BaseDeviceConfig( - val deviceConfig: DeviceConfig, - ) { - NEXUS_5(DeviceConfig.NEXUS_5), - PIXEL_C(DeviceConfig.PIXEL_C), - } - - @get:Rule - val paparazzi = Paparazzi( - maxPercentDifference = 0.0, - ) - - // @Test - fun preview_tests( - @TestParameter(valuesProvider = PreviewProvider::class) componentTestPreview: TestPreview, - @TestParameter baseDeviceConfig: BaseDeviceConfig, - @TestParameter(value = ["1.0", "1.5"]) fontScale: Float - ) { - paparazzi.unsafeUpdateConfig( - baseDeviceConfig.deviceConfig.copy( - softButtons = false, - ) - ) - paparazzi.snapshot { - val lifecycleOwner = LocalLifecycleOwner.current - CompositionLocalProvider( - LocalInspectionMode provides true, - LocalDensity provides Density( - density = LocalDensity.current.density, - fontScale = fontScale - ), - // Needed so that UI that uses it don't crash during screenshot tests - LocalOnBackPressedDispatcherOwner provides object : OnBackPressedDispatcherOwner { - override val lifecycle: Lifecycle - get() = lifecycleOwner.lifecycle - override val onBackPressedDispatcher: OnBackPressedDispatcher - get() = OnBackPressedDispatcher() - } - ) { - Box { - componentTestPreview.Content() - } - } - } - } -} - -interface TestPreview { - @Composable - fun Content() -} - -class ComponentTestPreview( - private val showkaseBrowserComponent: ShowkaseBrowserComponent -) : TestPreview { - @Composable - override fun Content() = showkaseBrowserComponent.component() - override fun toString(): String = showkaseBrowserComponent.componentKey -} - -class ColorTestPreview( - private val showkaseBrowserColor: ShowkaseBrowserColor -) : TestPreview { - @Composable - override fun Content() { - Box( - modifier = Modifier - .fillMaxWidth() - .height(250.dp) - .background(showkaseBrowserColor.color) - ) - } - - override fun toString(): String = "${showkaseBrowserColor.colorGroup}_${showkaseBrowserColor.colorName}" -} - -class TypographyTestPreview( - private val showkaseBrowserTypography: ShowkaseBrowserTypography -) : TestPreview { - @Composable - override fun Content() { - BasicText( - text = showkaseBrowserTypography.typographyName.replaceFirstChar { - it.titlecase(Locale.getDefault()) - }, - modifier = Modifier - .fillMaxWidth() - .padding(padding4x), - style = showkaseBrowserTypography.textStyle - ) - } - - override fun toString(): String = - "${showkaseBrowserTypography.typographyGroup}_${showkaseBrowserTypography.typographyName}" -} diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[1.Chips__Basic_Chip__Default_Style,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[1.Chips__Basic_Chip__Default_Style,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index a6ffc06c0..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[1.Chips__Basic_Chip__Default_Style,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[10.Buttons__CustomButton__Small,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[10.Buttons__CustomButton__Small,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index a57589c8a..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[10.Buttons__CustomButton__Small,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[11.Text__Sans_Serif_Text_Style__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[11.Text__Sans_Serif_Text_Style__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index dc2c229cc..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[11.Text__Sans_Serif_Text_Style__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[12.Text__Serif_Text_Style__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[12.Text__Serif_Text_Style__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 7521d5a90..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[12.Text__Serif_Text_Style__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[13.Rows__Simple_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[13.Rows__Simple_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index be6f3568b..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[13.Rows__Simple_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[14.Rows__Title_Subtitle_with_Thumbnail__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[14.Rows__Title_Subtitle_with_Thumbnail__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 8adb18fc6..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[14.Rows__Title_Subtitle_with_Thumbnail__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[15.Scrollable__Vertical_Scroll__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[15.Scrollable__Vertical_Scroll__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 9d864e953..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[15.Scrollable__Vertical_Scroll__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[16.Default_Group__Preview_-_1.5_font__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[16.Default_Group__Preview_-_1.5_font__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 8872acad3..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[16.Default_Group__Preview_-_1.5_font__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[17.Text__H6_Text_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[17.Text__H6_Text_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index cf463e40b..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[17.Text__H6_Text_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[18.Text__H6_Text_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[18.Text__H6_Text_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 6630e6962..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[18.Text__H6_Text_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[19.Text__H6_Text_Row_&_special_chars__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[19.Text__H6_Text_Row_&_special_chars__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index cf463e40b..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[19.Text__H6_Text_Row_&_special_chars__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[2.Chips__Basic_Chip__Yellow_Background,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[2.Chips__Basic_Chip__Yellow_Background,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 713af919d..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[2.Chips__Basic_Chip__Yellow_Background,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[20.Text__H6_Text_Row_&_special_chars__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[20.Text__H6_Text_Row_&_special_chars__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 6630e6962..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[20.Text__H6_Text_Row_&_special_chars__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[21.Light_Colors__Secondary,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[21.Light_Colors__Secondary,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index f5d6c27b4..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[21.Light_Colors__Secondary,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[22.Light_Colors__Secondary_Variant,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[22.Light_Colors__Secondary_Variant,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index ed343f670..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[22.Light_Colors__Secondary_Variant,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[23.Light_Colors__Background,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[23.Light_Colors__Background,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index c916c592b..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[23.Light_Colors__Background,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[24.Light_Colors__Surface,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[24.Light_Colors__Surface,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index c916c592b..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[24.Light_Colors__Surface,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[25.Light_Colors__Error,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[25.Light_Colors__Error,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 959e21872..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[25.Light_Colors__Error,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[26.Light_Colors__Primary,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[26.Light_Colors__Primary,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index f714faf8f..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[26.Light_Colors__Primary,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[27.Light_Colors__Primary_Variant,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[27.Light_Colors__Primary_Variant,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 89b4a8edf..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[27.Light_Colors__Primary_Variant,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[28.Material__H1,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[28.Material__H1,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index b8719bd91..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[28.Material__H1,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[29.Material__H2,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[29.Material__H2,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index c7a9c96a7..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[29.Material__H2,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[3.Rows__Bottom_Label_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[3.Rows__Bottom_Label_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 7880e6397..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[3.Rows__Bottom_Label_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[30.Material__H3,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[30.Material__H3,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 4e80bbc4a..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[30.Material__H3,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[31.Material__H4,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[31.Material__H4,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 349c6b331..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[31.Material__H4,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[32.Material__H5,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[32.Material__H5,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index e9702c565..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[32.Material__H5,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[33.Material__H6,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[33.Material__H6,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 2d2ccb61a..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[33.Material__H6,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[34.Material__Subtitle1,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[34.Material__Subtitle1,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 643d29bc9..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[34.Material__Subtitle1,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[35.Material__Subtitle2,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[35.Material__Subtitle2,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index cc87203e2..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[35.Material__Subtitle2,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[36.Material__Body1,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[36.Material__Body1,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index c3c4f0e0e..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[36.Material__Body1,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[37.Material__Body2,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[37.Material__Body2,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 22c4ecdd1..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[37.Material__Body2,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[38.Material__Button,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[38.Material__Button,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 0bfd0c8f6..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[38.Material__Button,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[39.Material__Caption,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[39.Material__Caption,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index e85dfbb4c..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[39.Material__Caption,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[4.Navigation__Bottom_Navigation_Bar__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[4.Navigation__Bottom_Navigation_Bar__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 106727e80..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[4.Navigation__Bottom_Navigation_Bar__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[40.Material__Overline,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[40.Material__Overline,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index d249db967..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[40.Material__Overline,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[6.Text__Cursive_Text_Style__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[6.Text__Cursive_Text_Style__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index e549714c2..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[6.Text__Cursive_Text_Style__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[7.Text__H4_Text_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[7.Text__H4_Text_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 76f905bf0..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[7.Text__H4_Text_Row__null,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[8.Buttons__CustomButton__Default_Style,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[8.Buttons__CustomButton__Default_Style,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 1164b0c0f..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[8.Buttons__CustomButton__Default_Style,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[9.Buttons__CustomButton__Medium,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[9.Buttons__CustomButton__Medium,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index 4e01c582a..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/images/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[9.Buttons__CustomButton__Medium,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/videos/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[5.Animated__AnimatedOffset__Default_Style,1.Pixel5,1.Ltr,1.DEFAULT].png b/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/videos/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[5.Animated__AnimatedOffset__Default_Style,1.Pixel5,1.Ltr,1.DEFAULT].png deleted file mode 100644 index f12e93ab3..000000000 Binary files a/showkase-screenshot-testing-paparazzi-sample/src/test/snapshots/videos/com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_MyPaparazziShowkaseScreenshotTestImpl_test_previews[5.Animated__AnimatedOffset__Default_Style,1.Pixel5,1.Ltr,1.DEFAULT].png and /dev/null differ diff --git a/showkase-screenshot-testing-paparazzi/.gitignore b/showkase-screenshot-testing-paparazzi/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/showkase-screenshot-testing-paparazzi/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/showkase-screenshot-testing-paparazzi/build.gradle b/showkase-screenshot-testing-paparazzi/build.gradle deleted file mode 100644 index a46727394..000000000 --- a/showkase-screenshot-testing-paparazzi/build.gradle +++ /dev/null @@ -1,90 +0,0 @@ -import com.vanniktech.maven.publish.AndroidMultiVariantLibrary - -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' - id 'org.jetbrains.kotlin.plugin.compose' - id 'com.vanniktech.maven.publish' -} - -configurations { - all { - // Added to resolve this error - // /home/runner/work/Showkase/Showkase/showkase-screenshot-testing-paparazzi/build.gradle: - // Error: commons-logging defines classes that conflict with classes now provided by Android. - // Solutions include finding newer versions or alternative libraries that don't have the same - // problem (for example, for httpclient use HttpUrlConnection or okhttp instead), or repackaging - // the library using something like jarjar. [DuplicatePlatformClasses] - exclude module: 'httpclient' - exclude module: 'commons-logging' - - // Added to resolve errors of the type - // Caused by: java.lang.RuntimeException: Duplicate class - // com.google.protobuf.AbstractMessageLite found in modules protobuf-java-3.4.0 - // (com.google.protobuf:protobuf-java:3.4.0) and protobuf-lite-3.0.1 - // (com.google.protobuf:protobuf-lite:3.0.1) - exclude module: 'protobuf-lite' - // Duplicate class javax.activation.ActivationDataFlavor found in modules - // javax.activation-1.2.0 (com.sun.activation:javax.activation:1.2.0) and - // javax.activation-api-1.2.0 (javax.activation:javax.activation-api:1.2.0) - exclude module: 'javax.activation' - // Duplicate class org.hamcrest.BaseDescription found in modules hamcrest-core-1.3 - // (org.hamcrest:hamcrest-core:1.3) and layoutlib-native-jdk11-2021.2.1-patch1-fa3aa65 - // (app.cash.paparazzi:layoutlib-native-jdk11:2021.2.1-patch1-fa3aa65) - exclude module: 'hamcrest-core' - // Duplicate class androidx.annotation.ChecksSdkIntAtLeast found in modules annotation-1.3.0 - // (androidx.annotation:annotation:1.3.0) and layoutlib-native-jdk11-2022.1.1-beta4-f5f9f71 - // (app.cash.paparazzi:layoutlib-native-jdk11:2022.1.1-beta4-f5f9f71) - exclude module: 'annotation' - } -} - -android { - namespace 'com.airbnb.android.showkase.screenshot.testing.paparazzi' - compileSdk 36 - - defaultConfig { - minSdk 21 - targetSdk 33 - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - debug { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - buildFeatures { - compose true - } - // Added to avoid this error - - // Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction - // > 2 files found with path 'META-INF/DEPENDENCIES - packagingOptions { - exclude 'META-INF/DEPENDENCIES' - } -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - // Showkase - api project(':showkase') - api deps.compose.foundation - api deps.compose.activityCompose - compileOnly deps.test.paparazzi - - // Testing - api deps.test.testParameterInjector - api deps.test.androidXTestRules - api deps.test.androidxTestRunner -} - -mavenPublishing { - configure(new AndroidMultiVariantLibrary(true, true)) -} \ No newline at end of file diff --git a/showkase-screenshot-testing-paparazzi/proguard-rules.pro b/showkase-screenshot-testing-paparazzi/proguard-rules.pro deleted file mode 100644 index 481bb4348..000000000 --- a/showkase-screenshot-testing-paparazzi/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/showkase-screenshot-testing-paparazzi/src/main/AndroidManifest.xml b/showkase-screenshot-testing-paparazzi/src/main/AndroidManifest.xml deleted file mode 100644 index a5918e68a..000000000 --- a/showkase-screenshot-testing-paparazzi/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/showkase-screenshot-testing-paparazzi/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/PaparazziShowkaseScreenshotTest.kt b/showkase-screenshot-testing-paparazzi/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/PaparazziShowkaseScreenshotTest.kt deleted file mode 100644 index 2d094190c..000000000 --- a/showkase-screenshot-testing-paparazzi/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/PaparazziShowkaseScreenshotTest.kt +++ /dev/null @@ -1,245 +0,0 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi - -import android.content.res.Configuration -import androidx.activity.OnBackPressedDispatcher -import androidx.activity.OnBackPressedDispatcherOwner -import androidx.activity.compose.LocalOnBackPressedDispatcherOwner -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.BasicText -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle -import app.cash.paparazzi.DeviceConfig -import app.cash.paparazzi.Paparazzi -import com.airbnb.android.showkase.annotation.ScreenshotConfig -import com.airbnb.android.showkase.annotation.ShowkaseScreenshot -import com.airbnb.android.showkase.models.ShowkaseBrowserColor -import com.airbnb.android.showkase.models.ShowkaseBrowserComponent -import com.airbnb.android.showkase.models.ShowkaseBrowserTypography -import com.airbnb.android.showkase.ui.padding4x -import com.android.ide.common.rendering.api.SessionParams -import java.util.Locale - -/** - * - * Interface that needs to be implemented for auto-generating screenshot tests that leverage - * [Paparazzi]. This is generally used along with the [ShowkaseScreenshot] annotation. You will - * typically add the implementation of this interface in your root module that has access to all - * your UI elements that you'd like to test. In addition, you need to make sure that the - * implementing class is an abstract/open class. Finally, the companion object of your implementation - * class needs to implement the [PaparazziShowkaseScreenshotTest.CompanionObject] interface. This - * interface provides a mechanism to override the default behavior if you need to. - * - *

- * Here's an example of how you would typically use it with the defaults: - * - * @ShowkaseScreenshotTest - * abstract class MyScreenshotTest: PaparazziShowkaseScreenshotTest { - * companion object: PaparazziShowkaseScreenshotTest.CompanionObject - * } - * - *

- * - * Note: Paparazzi requires your screenshot tests to be in a library module. Please ensure that the - * class that implements this interface is in a library module, other the Paparazzi integration - * won't work as expected. - * - */ -interface PaparazziShowkaseScreenshotTest { - - /** - * Interface that *must* be implemented by the companion object of your [PaparazziShowkaseScreenshotTest] - * implementation. - */ - interface CompanionObject { - - /** - * Returns the [Paparazzi] implementation that should be used when running the screenshot - * tests. - */ - fun providePaparazzi(): Paparazzi = Paparazzi( - maxPercentDifference = 0.0, - showSystemUi = false, - renderingMode = SessionParams.RenderingMode.SHRINK - ) - - /** - * The list of devices that we should run the screenshot tests on. It returns a list - * of [PaparazziShowkaseDeviceConfig], which is a wrapper for Paparazzi's [DeviceConfig]. - */ - fun deviceConfigs(): List = listOf( - PaparazziShowkaseDeviceConfig() - ) - - /** - * The list of layout directions that we should run the screenshot on. By default, the - * screenshots are only taken in left-to-right layout direction. - */ - fun layoutDirections(): List = listOf(LayoutDirection.Ltr) - - /** - * The list of [PaparazziShowkaseUIMode]'s that we should run the screenshots on. Other than - * default, you can also toggle dark mode through this setting for your screenshots. - */ - fun uiModes(): List = listOf(PaparazziShowkaseUIMode.DEFAULT) - } - - fun takePaparazziSnapshot( - paparazzi: Paparazzi, - testPreview: PaparazziShowkaseTestPreview, - direction: LayoutDirection, - mode: PaparazziShowkaseUIMode, - captureType: ScreenshotConfig = ScreenshotConfig.SingleStaticImage, - ) { - val hostView = ComposeView(paparazzi.context) - hostView.setContent { - PaparazziWrapper(mode, direction, testPreview) - } - when (captureType) { - ScreenshotConfig.SingleStaticImage -> paparazzi.snapshot(hostView) - is ScreenshotConfig.MultipleImagesAtOffsets -> captureType.offsetMillis.forEach { offsetMs -> - paparazzi.snapshot( - hostView, - name = "${offsetMs}ms", - offsetMillis = offsetMs.toLong() - ) - } - - is ScreenshotConfig.SingleAnimatedImage -> paparazzi.gif( - view = hostView, - end = captureType.durationMillis.toLong(), - fps = captureType.framerate - ) - } - } - - @Composable - fun PaparazziWrapper( - mode: PaparazziShowkaseUIMode, - direction: LayoutDirection, - testPreview: PaparazziShowkaseTestPreview - ) { - val lifecycleOwner = LocalLifecycleOwner.current - val configuration = if (mode == PaparazziShowkaseUIMode.DARK) { - Configuration(LocalConfiguration.current).apply { - uiMode = Configuration.UI_MODE_NIGHT_YES - } - } else { - LocalConfiguration.current - } - CompositionLocalProvider( - LocalInspectionMode provides true, - LocalDensity provides Density( - density = LocalDensity.current.density, - ), - LocalConfiguration provides configuration, - LocalLayoutDirection provides direction, - // Needed so that UI that uses it don't crash during screenshot tests - LocalOnBackPressedDispatcherOwner provides object : OnBackPressedDispatcherOwner { - override val lifecycle: Lifecycle - get() = lifecycleOwner.lifecycle - override val onBackPressedDispatcher: OnBackPressedDispatcher - get() = OnBackPressedDispatcher() - } - ) { - Box { - testPreview.Content() - } - } - } -} - -interface PaparazziShowkaseTestPreview { - @Composable - fun Content() - - val captureType: ScreenshotConfig - get() = ScreenshotConfig.SingleStaticImage -} - -private const val DELIM = "__" // Can't use * which is an invalid character for Paparazzi - -class ComponentPaparazziShowkaseTestPreview( - private val showkaseBrowserComponent: ShowkaseBrowserComponent, -) : PaparazziShowkaseTestPreview { - - @Composable - override fun Content() = showkaseBrowserComponent.component() - - override val captureType = showkaseBrowserComponent.screenshotConfig - - override fun toString(): String = - "${showkaseBrowserComponent.group}$DELIM${showkaseBrowserComponent.componentName}$DELIM" + - "${showkaseBrowserComponent.styleName}" -} - -class ColorPaparazziShowkaseTestPreview( - private val showkaseBrowserColor: ShowkaseBrowserColor -) : PaparazziShowkaseTestPreview { - @Composable - override fun Content() { - Box( - modifier = Modifier - .fillMaxWidth() - .height(250.dp) - .background(showkaseBrowserColor.color) - ) - } - - override fun toString(): String = - "${showkaseBrowserColor.colorGroup}${DELIM}${showkaseBrowserColor.colorName}" -} - -class TypographyPaparazziShowkaseTestPreview( - private val showkaseBrowserTypography: ShowkaseBrowserTypography -) : PaparazziShowkaseTestPreview { - @Composable - override fun Content() { - BasicText( - text = showkaseBrowserTypography.typographyName.replaceFirstChar { - it.titlecase(Locale.getDefault()) - }, - modifier = Modifier - .fillMaxWidth() - .padding(padding4x), - style = showkaseBrowserTypography.textStyle - ) - } - - override fun toString(): String = - "${showkaseBrowserTypography.typographyGroup}${DELIM}${showkaseBrowserTypography.typographyName}" -} - -/** - * Wrapper class for Paparazzi's [DeviceConfig]. This was needed so that we could have a more - * reasonable name for the test using the identifier that you pass to it. By default, the screenshots - * are taken on a Pixel 5 device (as per Paparazzi's definition). - */ -data class PaparazziShowkaseDeviceConfig( - val uniqueIdentifier: String = "Pixel5", - val deviceConfig: DeviceConfig = DeviceConfig.PIXEL_5 -) { - override fun toString() = uniqueIdentifier -} - -/** - * Enum to represent the [Configuration.uiMode] that the screenshot execute under. - */ -enum class PaparazziShowkaseUIMode { - DEFAULT, - DARK -} diff --git a/showkase-screenshot-testing-roborazzi-sample/build.gradle.kts b/showkase-screenshot-testing-roborazzi-sample/build.gradle.kts new file mode 100644 index 000000000..59a22af33 --- /dev/null +++ b/showkase-screenshot-testing-roborazzi-sample/build.gradle.kts @@ -0,0 +1,105 @@ +import com.android.build.api.variant.HasHostTests + +plugins { + id("com.android.library") + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.roborazzi) + id("com.google.devtools.ksp") +} + +android { + namespace = "com.airbnb.android.showkase.screenshot.testing.roborazzi.sample" + + defaultConfig { + minSdk = 23 + compileSdk = 36 + } + + buildFeatures { + compose = true + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } + + packaging { + resources { + excludes += listOf( + "META-INF/gradle/incremental.annotation.processors", + "META-INF/*.kotlin_module", + "META-INF/AL2.0", + "META-INF/LGPL2.1", + ) + } + } + lint { + baseline = file("lint-baseline.xml") + } +} + +kotlin { + jvmToolchain(21) +} + +// KSP-generated kotlin output isn't auto-registered with AGP's source model +// (https://github.com/google/ksp/issues/37). Wire each variant's main and +// host-test (unit-test) sources via the modern androidComponents API. The +// older variant.unitTest accessor was generalized to HasHostTests in AGP 9. +androidComponents { + onVariants { variant -> + variant.sources.java?.addStaticSourceDirectory( + layout.buildDirectory.dir("generated/ksp/${variant.name}/kotlin") + .get().asFile.absolutePath + ) + (variant as? HasHostTests)?.hostTests?.values?.forEach { hostTest -> + hostTest.sources.java?.addStaticSourceDirectory( + layout.buildDirectory.dir("generated/ksp/${hostTest.name}/kotlin") + .get().asFile.absolutePath + ) + } + + // AGP lint tasks read KSP-generated test sources, but Gradle can't infer + // the producer/consumer relationship from a srcDir registration alone. + val variantCap = variant.name.replaceFirstChar { it.uppercase() } + val kspTestTask = "ksp${variantCap}UnitTestKotlin" + listOf( + "generate${variantCap}UnitTestLintModel", + "lintAnalyze${variantCap}UnitTest", + ).forEach { consumer -> + tasks.matching { it.name == consumer }.configureEach { dependsOn(kspTestTask) } + } + } +} + +dependencies { + implementation(project(":showkase")) + ksp(project(":showkase-processor")) + kspTest(project(":showkase-processor")) + api(project(":showkase-screenshot-testing")) + api(project(":showkase-screenshot-testing-roborazzi")) + + implementation(libs.compose.activityCompose) + implementation(libs.compose.composeRuntime) + implementation(libs.compose.constraintLayout) + implementation(libs.compose.core) + implementation(libs.compose.foundation) + implementation(libs.compose.tooling) + implementation(libs.compose.layout) + implementation(libs.compose.material) + implementation(libs.compose.materialIconsCore) + implementation(libs.compose.savedInstanceState) + implementation(libs.compose.uiLiveData) + + implementation(libs.imageLoading.picasso) + + testImplementation(libs.test.junit) + testImplementation(libs.test.junitImplementation) + testImplementation(libs.test.roborazzi) + testImplementation(libs.test.roborazziCompose) + testImplementation(libs.test.roborazziJunitRule) + testImplementation(libs.test.robolectric) + testImplementation(libs.compose.uiTest) +} diff --git a/showkase-screenshot-testing-roborazzi-sample/lint-baseline.xml b/showkase-screenshot-testing-roborazzi-sample/lint-baseline.xml new file mode 100644 index 000000000..a9cfe5fa5 --- /dev/null +++ b/showkase-screenshot-testing-roborazzi-sample/lint-baseline.xml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/showkase-screenshot-testing-roborazzi-sample/src/main/AndroidManifest.xml b/showkase-screenshot-testing-roborazzi-sample/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8072ee00d --- /dev/null +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/HighFont.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/HighFont.kt similarity index 66% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/HighFont.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/HighFont.kt index cde032870..a7e7ac32c 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/HighFont.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/HighFont.kt @@ -1,4 +1,4 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi +package com.airbnb.android.showkase.screenshot.testing.roborazzi import androidx.compose.ui.tooling.preview.Preview diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/Text.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/Text.kt similarity index 84% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/Text.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/Text.kt index 7887cbf4b..619e2ec8b 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/Text.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/Text.kt @@ -1,4 +1,4 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi +package com.airbnb.android.showkase.screenshot.testing.roborazzi import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxWidth @@ -8,7 +8,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color // This is for testing that custom annotation -// works with paparazzi +// works with Roborazzi @HighFont @Composable fun Preview() { diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Animation.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Animation.kt similarity index 96% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Animation.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Animation.kt index f82dbf9a5..9f2f5efd2 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Animation.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Animation.kt @@ -1,4 +1,4 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/BasicChip.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/BasicChip.kt similarity index 95% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/BasicChip.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/BasicChip.kt index 2ddc43acb..99c9c70e9 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/BasicChip.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/BasicChip.kt @@ -1,4 +1,4 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Color.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Color.kt similarity index 91% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Color.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Color.kt index d0dd4f412..f610f5c1d 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Color.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Color.kt @@ -1,4 +1,4 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample import androidx.compose.ui.graphics.Color import com.airbnb.android.showkase.annotation.ShowkaseColor diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/CustomButton.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/CustomButton.kt similarity index 97% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/CustomButton.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/CustomButton.kt index 8bb499a87..e21bdee96 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/CustomButton.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/CustomButton.kt @@ -1,4 +1,4 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding diff --git a/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Dialog.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Dialog.kt new file mode 100644 index 000000000..0c560d462 --- /dev/null +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Dialog.kt @@ -0,0 +1,95 @@ +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.airbnb.android.showkase.annotation.ShowkaseDialog + +@Composable +fun ConfirmDialogContent( + title: String, + message: String, + confirmLabel: String, + dismissLabel: String, +) { + Surface( + shape = RoundedCornerShape(12.dp), + color = Color.White, + elevation = 8.dp, + modifier = Modifier.width(280.dp), + ) { + Column(modifier = Modifier.padding(20.dp)) { + Text( + text = title, + style = TextStyle(fontSize = 18.sp, fontWeight = FontWeight.Bold), + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = message, + style = TextStyle(fontSize = 14.sp, color = Color.DarkGray), + ) + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + Text( + text = dismissLabel, + style = TextStyle(fontSize = 14.sp, color = Color.Gray), + modifier = Modifier.padding(end = 16.dp), + ) + Text( + text = confirmLabel, + style = TextStyle( + fontSize = 14.sp, + color = Color(0xFF1565C0), + fontWeight = FontWeight.Bold, + ), + ) + } + } + } +} + +@ShowkaseDialog(name = "Confirm", group = "Dialogs", defaultStyle = true) +@Composable +fun ConfirmDialogPreview() { + ConfirmDialogContent( + title = "Delete item?", + message = "This action cannot be undone.", + confirmLabel = "Delete", + dismissLabel = "Cancel", + ) +} + +@ShowkaseDialog( + name = "Confirm", + group = "Dialogs", + styleName = "Custom labels", + buttonText = "Open Confirm Dialog", + hideButtonText = "Close", +) +@Composable +fun ConfirmDialogCustomLabelsPreview() { + ConfirmDialogContent( + title = "Sign out?", + message = "You will need to log in again to access your account.", + confirmLabel = "Sign out", + dismissLabel = "Stay", + ) +} diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Material.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Material.kt similarity index 97% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Material.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Material.kt index f689460eb..ba3ea6a7a 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Material.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Material.kt @@ -1,4 +1,4 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Navigation.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Navigation.kt similarity index 96% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Navigation.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Navigation.kt index 335c664c1..509ff3a38 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Navigation.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Navigation.kt @@ -1,4 +1,4 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample import androidx.compose.material.Icon import androidx.compose.runtime.Composable diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/PaparazziSampleRootModule.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/RoborazziSampleRootModule.kt similarity index 55% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/PaparazziSampleRootModule.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/RoborazziSampleRootModule.kt index aeed3e7ea..1201105af 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/PaparazziSampleRootModule.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/RoborazziSampleRootModule.kt @@ -1,7 +1,7 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample import com.airbnb.android.showkase.annotation.ShowkaseRoot import com.airbnb.android.showkase.annotation.ShowkaseRootModule @ShowkaseRoot -class PaparazziSampleRootModule : ShowkaseRootModule +class RoborazziSampleRootModule : ShowkaseRootModule diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Rows.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Rows.kt similarity index 98% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Rows.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Rows.kt index f10001fd0..a9d00bd0a 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Rows.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Rows.kt @@ -1,4 +1,4 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample import androidx.compose.material.Text import androidx.compose.foundation.background diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Scrollable.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Scrollable.kt similarity index 95% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Scrollable.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Scrollable.kt index 22eabbeb7..cc80b1e89 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Scrollable.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Scrollable.kt @@ -1,4 +1,4 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/ShowkaseTheme.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/ShowkaseTheme.kt similarity index 88% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/ShowkaseTheme.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/ShowkaseTheme.kt index 139d12daf..15b87b948 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/ShowkaseTheme.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/ShowkaseTheme.kt @@ -1,4 +1,4 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.MaterialTheme diff --git a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Text.kt b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Text.kt similarity index 98% rename from showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Text.kt rename to showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Text.kt index efb8ce664..c75a1dd5f 100644 --- a/showkase-screenshot-testing-paparazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/Text.kt +++ b/showkase-screenshot-testing-roborazzi-sample/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/Text.kt @@ -1,4 +1,4 @@ -package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample import androidx.compose.material.Text import androidx.compose.foundation.layout.fillMaxWidth diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/MyRoborazziShowkaseScreenshotTest.kt b/showkase-screenshot-testing-roborazzi-sample/src/test/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/MyRoborazziShowkaseScreenshotTest.kt new file mode 100644 index 000000000..585b517f1 --- /dev/null +++ b/showkase-screenshot-testing-roborazzi-sample/src/test/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/sample/MyRoborazziShowkaseScreenshotTest.kt @@ -0,0 +1,14 @@ +package com.airbnb.android.showkase.screenshot.testing.roborazzi.sample + +import com.airbnb.android.showkase.annotation.ShowkaseScreenshot +import com.airbnb.android.showkase.screenshot.testing.roborazzi.RoborazziShowkaseScreenshotTest + +/** + * Marker class for the Showkase processor. The accompanying companion object is what supplies the + * runtime Robolectric qualifiers and Roborazzi comparison options to the auto-generated + * `MyRoborazziShowkaseScreenshotTestImpl`. + */ +@ShowkaseScreenshot(rootShowkaseClass = RoborazziSampleRootModule::class) +abstract class MyRoborazziShowkaseScreenshotTest : RoborazziShowkaseScreenshotTest { + companion object : RoborazziShowkaseScreenshotTest.CompanionObject +} diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Background.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Background.png new file mode 100644 index 000000000..221b2eaae Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Background.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Error.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Error.png new file mode 100644 index 000000000..05692af53 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Error.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Primary.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Primary.png new file mode 100644 index 000000000..1a81f2aed Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Primary.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Primary_Variant.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Primary_Variant.png new file mode 100644 index 000000000..1b7c70a15 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Primary_Variant.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Secondary.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Secondary.png new file mode 100644 index 000000000..4544d21df Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Secondary.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Secondary_Variant.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Secondary_Variant.png new file mode 100644 index 000000000..892751e2d Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Secondary_Variant.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Surface.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Surface.png new file mode 100644 index 000000000..221b2eaae Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Light_Colors__Surface.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Body1.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Body1.png new file mode 100644 index 000000000..7175ad420 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Body1.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Body2.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Body2.png new file mode 100644 index 000000000..aad93a9bf Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Body2.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Button.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Button.png new file mode 100644 index 000000000..7dbb4b47b Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Button.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Caption.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Caption.png new file mode 100644 index 000000000..9b58ae171 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Caption.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H1.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H1.png new file mode 100644 index 000000000..001ca38ea Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H1.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H2.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H2.png new file mode 100644 index 000000000..3a251cf53 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H2.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H3.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H3.png new file mode 100644 index 000000000..dae06b682 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H3.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H4.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H4.png new file mode 100644 index 000000000..81c9898cf Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H4.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H5.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H5.png new file mode 100644 index 000000000..7d984f57c Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H5.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H6.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H6.png new file mode 100644 index 000000000..a4031641c Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__H6.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Overline.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Overline.png new file mode 100644 index 000000000..f413708eb Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Overline.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Subtitle1.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Subtitle1.png new file mode 100644 index 000000000..034400e0e Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Subtitle1.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Subtitle2.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Subtitle2.png new file mode 100644 index 000000000..24141edfe Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/Material__Subtitle2.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BasicChipPreview_null_Chips_BasicChip_0_DefaultStyle.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BasicChipPreview_null_Chips_BasicChip_0_DefaultStyle.png new file mode 100644 index 000000000..04d3eb31c Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BasicChipPreview_null_Chips_BasicChip_0_DefaultStyle.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BasicChipYellowPreview_null_Chips_BasicChip_0_YellowBackground.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BasicChipYellowPreview_null_Chips_BasicChip_0_YellowBackground.png new file mode 100644 index 000000000..301b038a3 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BasicChipYellowPreview_null_Chips_BasicChip_0_YellowBackground.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BottomLabelRowPreview_null_Rows_BottomLabelRow_0_null.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BottomLabelRowPreview_null_Rows_BottomLabelRow_0_null.png new file mode 100644 index 000000000..db914ddff Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BottomLabelRowPreview_null_Rows_BottomLabelRow_0_null.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BottomNavigationAlwaysShowLabelComponentPreview_null_Navigation_BottomNavigationBar_0_null.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BottomNavigationAlwaysShowLabelComponentPreview_null_Navigation_BottomNavigationBar_0_null.png new file mode 100644 index 000000000..aba11f2e2 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BottomNavigationAlwaysShowLabelComponentPreview_null_Navigation_BottomNavigationBar_0_null.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BoxWithAnimatedOffsetPreview_null_Animated_AnimatedOffset_0_DefaultStyle.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BoxWithAnimatedOffsetPreview_null_Animated_AnimatedOffset_0_DefaultStyle.png new file mode 100644 index 000000000..b961012fb Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_BoxWithAnimatedOffsetPreview_null_Animated_AnimatedOffset_0_DefaultStyle.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_ConfirmDialogCustomLabelsPreview_null_Dialogs_Confirm_0_Customlabels.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_ConfirmDialogCustomLabelsPreview_null_Dialogs_Confirm_0_Customlabels.png new file mode 100644 index 000000000..2562ade8e Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_ConfirmDialogCustomLabelsPreview_null_Dialogs_Confirm_0_Customlabels.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_ConfirmDialogPreview_null_Dialogs_Confirm_0_DefaultStyle.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_ConfirmDialogPreview_null_Dialogs_Confirm_0_DefaultStyle.png new file mode 100644 index 000000000..d9f968aaa Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_ConfirmDialogPreview_null_Dialogs_Confirm_0_DefaultStyle.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_CursiveTextComponent_null_Text_CursiveTextStyle_0_null.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_CursiveTextComponent_null_Text_CursiveTextStyle_0_null.png new file mode 100644 index 000000000..69b696a1c Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_CursiveTextComponent_null_Text_CursiveTextStyle_0_null.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H4TextRowComponentPreview_null_Text_H4TextRow_0_null.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H4TextRowComponentPreview_null_Text_H4TextRow_0_null.png new file mode 100644 index 000000000..453cf9c95 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H4TextRowComponentPreview_null_Text_H4TextRow_0_null.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H6TextRowComponentPreviewWithSpecialCharInPreview_null_Text_H6TextRow&specialchars_0_null_0.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H6TextRowComponentPreviewWithSpecialCharInPreview_null_Text_H6TextRow&specialchars_0_null_0.png new file mode 100644 index 000000000..9f3bf09d0 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H6TextRowComponentPreviewWithSpecialCharInPreview_null_Text_H6TextRow&specialchars_0_null_0.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H6TextRowComponentPreviewWithSpecialCharInPreview_null_Text_H6TextRow&specialchars_0_null_1.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H6TextRowComponentPreviewWithSpecialCharInPreview_null_Text_H6TextRow&specialchars_0_null_1.png new file mode 100644 index 000000000..4609e4503 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H6TextRowComponentPreviewWithSpecialCharInPreview_null_Text_H6TextRow&specialchars_0_null_1.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H6TextRowComponentPreview_null_Text_H6TextRow_0_null_0.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H6TextRowComponentPreview_null_Text_H6TextRow_0_null_0.png new file mode 100644 index 000000000..9f3bf09d0 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H6TextRowComponentPreview_null_Text_H6TextRow_0_null_0.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H6TextRowComponentPreview_null_Text_H6TextRow_0_null_1.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H6TextRowComponentPreview_null_Text_H6TextRow_0_null_1.png new file mode 100644 index 000000000..4609e4503 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_H6TextRowComponentPreview_null_Text_H6TextRow_0_null_1.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_PreviewCustomButtonDefault_null_Buttons_CustomButton_0_DefaultStyle.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_PreviewCustomButtonDefault_null_Buttons_CustomButton_0_DefaultStyle.png new file mode 100644 index 000000000..409a3f431 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_PreviewCustomButtonDefault_null_Buttons_CustomButton_0_DefaultStyle.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_PreviewCustomButtonMedium_null_Buttons_CustomButton_0_Medium.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_PreviewCustomButtonMedium_null_Buttons_CustomButton_0_Medium.png new file mode 100644 index 000000000..ff7c82880 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_PreviewCustomButtonMedium_null_Buttons_CustomButton_0_Medium.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_PreviewCustomButtonSmall_null_Buttons_CustomButton_0_Small.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_PreviewCustomButtonSmall_null_Buttons_CustomButton_0_Small.png new file mode 100644 index 000000000..bf94171ef Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_PreviewCustomButtonSmall_null_Buttons_CustomButton_0_Small.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_SansSerifTextComponentPreview_null_Text_SansSerifTextStyle_0_null.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_SansSerifTextComponentPreview_null_Text_SansSerifTextStyle_0_null.png new file mode 100644 index 000000000..4cedae334 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_SansSerifTextComponentPreview_null_Text_SansSerifTextStyle_0_null.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_SerifTextComponentPreview_null_Text_SerifTextStyle_0_null.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_SerifTextComponentPreview_null_Text_SerifTextStyle_0_null.png new file mode 100644 index 000000000..1c0aac3f4 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_SerifTextComponentPreview_null_Text_SerifTextStyle_0_null.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_SimpleRowPreview_null_Rows_SimpleRow_0_null.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_SimpleRowPreview_null_Rows_SimpleRow_0_null.png new file mode 100644 index 000000000..101450ba1 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_SimpleRowPreview_null_Rows_SimpleRow_0_null.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_TitleSubtitleThumbnailRowPreview_null_Rows_TitleSubtitlewithThumbnail_0_null.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_TitleSubtitleThumbnailRowPreview_null_Rows_TitleSubtitlewithThumbnail_0_null.png new file mode 100644 index 000000000..a95056b1e Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_TitleSubtitleThumbnailRowPreview_null_Rows_TitleSubtitlewithThumbnail_0_null.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_VerticalScrollPreview_null_Scrollable_VerticalScroll_0_null.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_VerticalScrollPreview_null_Scrollable_VerticalScroll_0_null.png new file mode 100644 index 000000000..01c089e76 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi.sample_VerticalScrollPreview_null_Scrollable_VerticalScroll_0_null.png differ diff --git a/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi_Preview_null_DefaultGroup_Preview-1.5font_0_null.png b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi_Preview_null_DefaultGroup_Preview-1.5font_0_null.png new file mode 100644 index 000000000..7f78cea64 Binary files /dev/null and b/showkase-screenshot-testing-roborazzi-sample/src/test/snapshots/roborazzi/com.airbnb.android.showkase.screenshot.testing.roborazzi_Preview_null_DefaultGroup_Preview-1.5font_0_null.png differ diff --git a/showkase-screenshot-testing-roborazzi/build.gradle.kts b/showkase-screenshot-testing-roborazzi/build.gradle.kts new file mode 100644 index 000000000..da7710c4b --- /dev/null +++ b/showkase-screenshot-testing-roborazzi/build.gradle.kts @@ -0,0 +1,56 @@ +import com.vanniktech.maven.publish.AndroidMultiVariantLibrary +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.SourcesJar + +plugins { + id("com.android.library") + alias(libs.plugins.kotlin.compose) + id("com.vanniktech.maven.publish") +} + +android { + namespace = "com.airbnb.android.showkase.screenshot.testing.roborazzi" + compileSdk = 36 + + defaultConfig { + minSdk = 23 + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + getByName("debug") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + buildFeatures { + compose = true + } + lint { + baseline = file("lint-baseline.xml") + } +} + +kotlin { + jvmToolchain(21) +} + +dependencies { + api(project(":showkase")) + api(libs.compose.foundation) + + // Roborazzi APIs are exposed via the generated test impl in consumers, but the + // hand-written wrapper types here also reference them at compile time, so they + // need to be on the api classpath of the consumer's test source set. + api(libs.test.roborazzi) + api(libs.test.roborazziCompose) + api(libs.test.roborazziJunitRule) + api(libs.test.robolectric) + api(libs.test.junit) + api(libs.compose.uiTest) +} + +mavenPublishing { + configure(AndroidMultiVariantLibrary(JavadocJar.Empty(), SourcesJar.Sources())) +} diff --git a/showkase-screenshot-testing-paparazzi/consumer-rules.pro b/showkase-screenshot-testing-roborazzi/consumer-rules.pro similarity index 100% rename from showkase-screenshot-testing-paparazzi/consumer-rules.pro rename to showkase-screenshot-testing-roborazzi/consumer-rules.pro diff --git a/showkase-screenshot-testing-roborazzi/lint-baseline.xml b/showkase-screenshot-testing-roborazzi/lint-baseline.xml new file mode 100644 index 000000000..75b4e0864 --- /dev/null +++ b/showkase-screenshot-testing-roborazzi/lint-baseline.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/showkase-screenshot-testing-roborazzi/src/main/AndroidManifest.xml b/showkase-screenshot-testing-roborazzi/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8072ee00d --- /dev/null +++ b/showkase-screenshot-testing-roborazzi/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/showkase-screenshot-testing-roborazzi/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/RoborazziShowkaseScreenshotTest.kt b/showkase-screenshot-testing-roborazzi/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/RoborazziShowkaseScreenshotTest.kt new file mode 100644 index 000000000..9fb92820c --- /dev/null +++ b/showkase-screenshot-testing-roborazzi/src/main/java/com/airbnb/android/showkase/screenshot/testing/roborazzi/RoborazziShowkaseScreenshotTest.kt @@ -0,0 +1,147 @@ +package com.airbnb.android.showkase.screenshot.testing.roborazzi + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.BasicText +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.airbnb.android.showkase.annotation.ScreenshotConfig +import com.airbnb.android.showkase.models.ShowkaseBrowserColor +import com.airbnb.android.showkase.models.ShowkaseBrowserComponent +import com.airbnb.android.showkase.models.ShowkaseBrowserTypography +import com.github.takahirom.roborazzi.RoborazziOptions + +/** + * Marker interface for the abstract class that the Showkase processor uses to generate a + * [Roborazzi](https://github.com/takahirom/roborazzi)-backed screenshot test. + * + * Typical usage: + * + * ``` + * @ShowkaseScreenshot(rootShowkaseClass = MyRootModule::class) + * abstract class MyRoborazziScreenshotTest : RoborazziShowkaseScreenshotTest { + * companion object : RoborazziShowkaseScreenshotTest.CompanionObject + * } + * ``` + * + * The processor emits a `MyRoborazziScreenshotTestImpl` class wired to a Robolectric runner that + * iterates every preview the Showkase root knows about and writes a PNG via Roborazzi's + * `captureRoboImage`. + * + * Device profile / locale / RTL / dark-mode are controlled through Robolectric + * `@Config(qualifiers = ...)` annotations on the test class, not via runtime API. Override + * [CompanionObject.qualifiers] to change the device profile for the generated test. + */ +interface RoborazziShowkaseScreenshotTest { + + interface CompanionObject { + + /** + * Robolectric `@Config(qualifiers = ...)` value applied to the generated test class. + * Defaults to `RobolectricDeviceQualifiers.Pixel5`. See + * [com.github.takahirom.roborazzi.RobolectricDeviceQualifiers] for available presets, and + * append modifiers like `+night`, `+ldrtl`, `+fontScale1.5` as needed. + */ + fun qualifiers(): String = + com.github.takahirom.roborazzi.RobolectricDeviceQualifiers.Pixel5 + + /** + * Robolectric `@Config(sdk = [...])` SDK level applied to the generated test class. + * Default 33 is a stable compromise between Compose support and Robolectric coverage. + */ + fun sdk(): Int = 33 + + /** + * Roborazzi options for comparison. Defaults to `changeThreshold = 0.01f` (1%) to absorb + * sub-pixel font anti-aliasing noise when goldens are recorded on one OS (e.g. macOS) and + * verified on another (e.g. Linux CI) — Roborazzi's `GraphicsMode.NATIVE` defers to the + * host Skia, so AA differs across platforms even when output is visually identical. + * Override to tighten for pixel-perfect comparison or loosen further for noisier renderings. + */ + fun roborazziOptions(): RoborazziOptions = RoborazziOptions( + compareOptions = RoborazziOptions.CompareOptions(changeThreshold = 0.01f), + ) + } +} + +/** + * Common interface implemented by component, color, and typography wrappers so the generated + * Roborazzi test can iterate everything Showkase knows about under one parameter type. + */ +interface RoborazziShowkaseTestPreview { + @Composable + fun Content() + + /** Stable identifier used as the snapshot file name. */ + val id: String + + val captureType: ScreenshotConfig + get() = ScreenshotConfig.SingleStaticImage +} + +private const val FILE_NAME_DELIM = "__" + +private fun String.sanitizeForFileName(): String = + replace(' ', '_').replace('/', '_').replace('\\', '_').replace(':', '_') + +class ComponentRoborazziShowkaseTestPreview( + private val showkaseBrowserComponent: ShowkaseBrowserComponent, +) : RoborazziShowkaseTestPreview { + @Composable + override fun Content() = showkaseBrowserComponent.component() + + override val captureType: ScreenshotConfig = showkaseBrowserComponent.screenshotConfig + + // componentKey is Showkase's guaranteed-unique identifier (it embeds the FQCN, group, name, + // styleName, and parameter index for previews coming from a preview-parameter provider). + override val id: String = showkaseBrowserComponent.componentKey.sanitizeForFileName() + + override fun toString(): String = id +} + +class ColorRoborazziShowkaseTestPreview( + private val showkaseBrowserColor: ShowkaseBrowserColor, +) : RoborazziShowkaseTestPreview { + @Composable + override fun Content() { + Box( + modifier = Modifier + .fillMaxWidth() + .height(250.dp) + .background(showkaseBrowserColor.color), + ) + } + + override val id: String = listOf( + showkaseBrowserColor.colorGroup, + showkaseBrowserColor.colorName, + ).joinToString(FILE_NAME_DELIM) { it.sanitizeForFileName() } + + override fun toString(): String = id +} + +class TypographyRoborazziShowkaseTestPreview( + private val showkaseBrowserTypography: ShowkaseBrowserTypography, +) : RoborazziShowkaseTestPreview { + @Composable + override fun Content() { + BasicText( + text = showkaseBrowserTypography.typographyName, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + style = showkaseBrowserTypography.textStyle, + ) + } + + override val id: String = listOf( + showkaseBrowserTypography.typographyGroup, + showkaseBrowserTypography.typographyName, + ).joinToString(FILE_NAME_DELIM) { it.sanitizeForFileName() } + + override fun toString(): String = id +} diff --git a/showkase-screenshot-testing-shot/build.gradle b/showkase-screenshot-testing-shot/build.gradle deleted file mode 100644 index 5c7ec4c86..000000000 --- a/showkase-screenshot-testing-shot/build.gradle +++ /dev/null @@ -1,67 +0,0 @@ -import com.vanniktech.maven.publish.AndroidMultiVariantLibrary - -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'shot' - id 'org.jetbrains.kotlin.plugin.compose' - id 'com.vanniktech.maven.publish' -} - -android { - namespace "com.airbnb.android.showkase.screenshot.testing.shot" - // Added to avoid this error - - // Execution failed for task ':showkase-processor-testing:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade - // > More than one file was found with OS independent path 'META-INF/gradle/incremental.annotation.processors' - packagingOptions { - exclude 'META-INF/gradle/incremental.annotation.processors' - exclude("META-INF/*.kotlin_module") - } - defaultConfig { - testApplicationId "com.airbnb.android.showkase.screenshot.testing.shot" - minSdkVersion 21 - compileSdk 36 - targetSdkVersion 33 - testInstrumentationRunner "com.karumi.shot.ShotTestRunner" - // The following argument makes the Android Test Orchestrator run its - // "pm clear" command after each test invocation. This command ensures - // that the app's state is completely cleared between tests. - testInstrumentationRunnerArguments clearPackageData: 'true' - } - shot { - applicationId = "com.airbnb.android.showkase.screenshot.testing.shot" - } - buildFeatures { - compose true - } - // Added to avoid this error - - // Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction - // > 2 files found with path 'META-INF/AL2.0' from inputs: - packagingOptions { - exclude 'META-INF/AL2.0' - exclude 'META-INF/LGPL2.1' - } -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - // Showkase - api project(':showkase') - api deps.compose.uiTest - api project(':showkase-screenshot-testing') - api deps.test.shotAndroid - - // Testing - api deps.test.androidXTestCore - api deps.test.androidXTestRules - api deps.test.androidxTestRunner -} - -mavenPublishing { - configure(new AndroidMultiVariantLibrary(true, true)) -} diff --git a/showkase-screenshot-testing-shot/build.gradle.kts b/showkase-screenshot-testing-shot/build.gradle.kts new file mode 100644 index 000000000..910978970 --- /dev/null +++ b/showkase-screenshot-testing-shot/build.gradle.kts @@ -0,0 +1,63 @@ +import com.vanniktech.maven.publish.AndroidMultiVariantLibrary +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.SourcesJar + +plugins { + id("com.android.library") + id("shot") + alias(libs.plugins.kotlin.compose) + id("com.vanniktech.maven.publish") +} + +android { + namespace = "com.airbnb.android.showkase.screenshot.testing.shot" + + defaultConfig { + testApplicationId = "com.airbnb.android.showkase.screenshot.testing.shot" + minSdk = 23 + compileSdk = 36 + testInstrumentationRunner = "com.karumi.shot.ShotTestRunner" + testInstrumentationRunnerArguments["clearPackageData"] = "true" + } + + buildFeatures { + compose = true + } + + packaging { + resources { + excludes += listOf( + "META-INF/gradle/incremental.annotation.processors", + "META-INF/*.kotlin_module", + "META-INF/AL2.0", + "META-INF/LGPL2.1", + ) + } + } + lint { + baseline = file("lint-baseline.xml") + } +} + +shot { + applicationId = "com.airbnb.android.showkase.screenshot.testing.shot" +} + +kotlin { + jvmToolchain(21) +} + +dependencies { + api(project(":showkase")) + api(libs.compose.uiTest) + api(project(":showkase-screenshot-testing")) + api(libs.test.shotAndroid) + + api(libs.test.androidXTestCore) + api(libs.test.androidXTestRules) + api(libs.test.androidxTestRunner) +} + +mavenPublishing { + configure(AndroidMultiVariantLibrary(JavadocJar.Empty(), SourcesJar.Sources())) +} diff --git a/showkase-screenshot-testing-shot/lint-baseline.xml b/showkase-screenshot-testing-shot/lint-baseline.xml new file mode 100644 index 000000000..aced025c1 --- /dev/null +++ b/showkase-screenshot-testing-shot/lint-baseline.xml @@ -0,0 +1,5 @@ + + + + diff --git a/showkase-screenshot-testing-shot/src/main/java/com.airbnb.android.showkase.screenshot.testing.shot/ShotShowkaseScreenshotTest.kt b/showkase-screenshot-testing-shot/src/main/java/com.airbnb.android.showkase.screenshot.testing.shot/ShotShowkaseScreenshotTest.kt index 4b2854a1c..c4db76df9 100644 --- a/showkase-screenshot-testing-shot/src/main/java/com.airbnb.android.showkase.screenshot.testing.shot/ShotShowkaseScreenshotTest.kt +++ b/showkase-screenshot-testing-shot/src/main/java/com.airbnb.android.showkase.screenshot.testing.shot/ShotShowkaseScreenshotTest.kt @@ -1,25 +1,15 @@ package com.airbnb.android.showkase.screenshot.testing.shot -import android.graphics.Bitmap import android.os.Build import androidx.annotation.RequiresApi +import com.airbnb.android.showkase.screenshot.testing.ScreenshotMetadata import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotTest -import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotType import com.karumi.shot.ScreenshotTest abstract class ShotShowkaseScreenshotTest : ShowkaseScreenshotTest, ScreenshotTest { @RequiresApi(Build.VERSION_CODES.O) - override fun onScreenshot( - id: String, - name: String, - group: String, - styleName: String?, - tags: List, - extraMetadata: List, - screenshotType: ShowkaseScreenshotType, - screenshotBitmap: Bitmap - ) { + override fun onScreenshot(metadata: ScreenshotMetadata) { compareScreenshot(composeTestRule) } } diff --git a/showkase-screenshot-testing/build.gradle b/showkase-screenshot-testing/build.gradle deleted file mode 100644 index f8b44eafd..000000000 --- a/showkase-screenshot-testing/build.gradle +++ /dev/null @@ -1,63 +0,0 @@ -import com.vanniktech.maven.publish.AndroidMultiVariantLibrary - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'org.jetbrains.kotlin.plugin.compose' -apply plugin: 'com.vanniktech.maven.publish' - -android { - namespace "com.airbnb.android.showkase.screenshot.testing" - // Added to avoid this error - - // Execution failed for task ':showkase-processor-testing:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade - // > More than one file was found with OS independent path 'META-INF/gradle/incremental.annotation.processors' - packagingOptions { - exclude 'META-INF/gradle/incremental.annotation.processors' - exclude("META-INF/*.kotlin_module") - } - defaultConfig { - minSdkVersion 21 - compileSdk 36 - targetSdkVersion 33 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - // The following argument makes the Android Test Orchestrator run its - // "pm clear" command after each test invocation. This command ensures - // that the app's state is completely cleared between tests. - testInstrumentationRunnerArguments clearPackageData: 'true' - } - buildFeatures { - compose true - } - // Added to avoid this error - - // Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction - // > 2 files found with path 'META-INF/AL2.0' from inputs: - packagingOptions { - exclude 'META-INF/AL2.0' - exclude 'META-INF/LGPL2.1' - } -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - // Showkase - api project(':showkase') - api deps.compose.uiTest - implementation deps.compose.core - implementation deps.compose.foundation - - // Testing - testImplementation deps.test.assertJ - testImplementation deps.test.googleTruth - api deps.test.junit - api deps.test.androidXTestCore - api deps.test.androidXTestRules - api deps.test.androidxTestRunner -} - -mavenPublishing { - configure(new AndroidMultiVariantLibrary(true, true)) -} diff --git a/showkase-screenshot-testing/build.gradle.kts b/showkase-screenshot-testing/build.gradle.kts new file mode 100644 index 000000000..752e649a1 --- /dev/null +++ b/showkase-screenshot-testing/build.gradle.kts @@ -0,0 +1,60 @@ +import com.vanniktech.maven.publish.AndroidMultiVariantLibrary +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.SourcesJar + +plugins { + id("com.android.library") + alias(libs.plugins.kotlin.compose) + id("com.vanniktech.maven.publish") +} + +android { + namespace = "com.airbnb.android.showkase.screenshot.testing" + + defaultConfig { + minSdk = 23 + compileSdk = 36 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["clearPackageData"] = "true" + } + + buildFeatures { + compose = true + } + + packaging { + resources { + excludes += listOf( + "META-INF/gradle/incremental.annotation.processors", + "META-INF/*.kotlin_module", + "META-INF/AL2.0", + "META-INF/LGPL2.1", + ) + } + } + lint { + baseline = file("lint-baseline.xml") + } +} + +kotlin { + jvmToolchain(21) +} + +dependencies { + api(project(":showkase")) + api(libs.compose.uiTest) + implementation(libs.compose.core) + implementation(libs.compose.foundation) + + testImplementation(libs.test.assertJ) + testImplementation(libs.test.googleTruth) + api(libs.test.junit) + api(libs.test.androidXTestCore) + api(libs.test.androidXTestRules) + api(libs.test.androidxTestRunner) +} + +mavenPublishing { + configure(AndroidMultiVariantLibrary(JavadocJar.Empty(), SourcesJar.Sources())) +} diff --git a/showkase-screenshot-testing/lint-baseline.xml b/showkase-screenshot-testing/lint-baseline.xml new file mode 100644 index 000000000..225c81f8b --- /dev/null +++ b/showkase-screenshot-testing/lint-baseline.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/showkase-screenshot-testing/src/main/java/com/airbnb/android/showkase/screenshot/testing/ScreenshotMetadata.kt b/showkase-screenshot-testing/src/main/java/com/airbnb/android/showkase/screenshot/testing/ScreenshotMetadata.kt new file mode 100644 index 000000000..2cea9fc64 --- /dev/null +++ b/showkase-screenshot-testing/src/main/java/com/airbnb/android/showkase/screenshot/testing/ScreenshotMetadata.kt @@ -0,0 +1,27 @@ +package com.airbnb.android.showkase.screenshot.testing + +import android.graphics.Bitmap + +/** + * Metadata passed to [ShowkaseScreenshotTest.onScreenshot] when a screenshot has been captured. + * + * @param id A stable id for this screenshot. For composables, this is the component's + * `componentKey`. For colors/typography, this is a composite of group and name. + * @param name The name of the UI element. + * @param group The group the element belongs to. + * @param styleName The style name (Composables only; null for color/typography). + * @param tags Tags declared on the `@ShowkaseComposable` annotation (Composables only). + * @param extraMetadata Arbitrary extra metadata declared on the `@ShowkaseComposable` annotation. + * @param screenshotType The type of UI element being captured. + * @param screenshotBitmap The captured bitmap. + */ +public data class ScreenshotMetadata( + val id: String, + val name: String, + val group: String, + val styleName: String? = null, + val tags: List = emptyList(), + val extraMetadata: List = emptyList(), + val screenshotType: ShowkaseScreenshotType, + val screenshotBitmap: Bitmap, +) diff --git a/showkase-screenshot-testing/src/main/java/com/airbnb/android/showkase/screenshot/testing/ShowkaseScreenshotTest.kt b/showkase-screenshot-testing/src/main/java/com/airbnb/android/showkase/screenshot/testing/ShowkaseScreenshotTest.kt index 98f8f64ef..8f95ff595 100644 --- a/showkase-screenshot-testing/src/main/java/com/airbnb/android/showkase/screenshot/testing/ShowkaseScreenshotTest.kt +++ b/showkase-screenshot-testing/src/main/java/com/airbnb/android/showkase/screenshot/testing/ShowkaseScreenshotTest.kt @@ -19,9 +19,10 @@ import com.airbnb.android.showkase.annotation.ShowkaseScreenshot import com.airbnb.android.showkase.models.ShowkaseBrowserColor import com.airbnb.android.showkase.models.ShowkaseBrowserComponent import com.airbnb.android.showkase.models.ShowkaseBrowserTypography -import com.airbnb.android.showkase.ui.padding4x import org.junit.Rule -import java.util.* +import java.util.Locale + +private val componentPadding = 16.dp /** * @@ -33,17 +34,8 @@ import java.util.* * Here's an example of how you would typically use it: * * @ShowkaseScreenshotTest - * abstract class MyScreenshotTest: ShowkaseScreenshotModule { - * override fun onScreenshot( - * id: String, - * name: String, - * group: String, - * styleName: String? = null, - * tags: List = emptyList(), - * extraMetadata: List = emptyList(), - * screenshotType: ShowkaseScreenshotType, - * screenshotBitmap: Bitmap - * ) { + * abstract class MyScreenshotTest: ShowkaseScreenshotTest { + * override fun onScreenshot(metadata: ScreenshotMetadata) { * // Here you do the action you want to take with the screenshot. * } * } @@ -51,37 +43,44 @@ import java.util.* *

* * Note: you should add this class to the androidTest sourceSet as that's where your testing - * dependencies will exists otherwise the generate test won't compile.Additionally,Its important + * dependencies will exist otherwise the generated test won't compile. Additionally, it's important * that the class you annotate with [ShowkaseScreenshot] is either abstract or open as Showkase - * generates a class that extends this class in order to get access to theonScreenshot method. + * generates a class that extends this class in order to get access to the onScreenshot method. */ -@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.TooGenericExceptionThrown", "Detekt.LongParameterList") +@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.TooGenericExceptionThrown") interface ShowkaseScreenshotTest { @get:Rule val composeTestRule: ComposeContentTestRule /** - * This method is called during the execution of each screenshot test after the screenshot of - * the UI element has been successfully taken. Things that you'd typically want to do here include, - * but not limited to, the following: - * - Calling an API service that your app uses for matching screenshots - * - Storing the screenshot on device to generate golden copies of images - * - Using the new screenshot and comparing/asserting against a golden copy of the same element - * - * @param id a unique id to represent this screenshot. There are no guarantees that the id will - * be identical across screenshots for the same UI element. - * @param name name of the UI element. - * @param styleName The name of the style that this component represents. This is only available - * when ShowkaseScreenshotType == Composable. - * @param group group that this UI element belongs to - * @param tags The list of tags set on the component. This is only available when - * ShowkaseScreenshotType == Composable. - * @param extraMetadata The list of extra metadata set on the component. This is only available when - * ShowkaseScreenshotType == Composable. - * @param screenshotType A screenshot can be one of the following types: Composable, Color or Typography - * @param screenshotBitmap Bitmap of the given UI element + * Called during the execution of each screenshot test after the screenshot has been captured. + * Implementations typically save the bitmap to disk, compare it against a golden, or upload + * it to a screenshot service. */ + fun onScreenshot(metadata: ScreenshotMetadata) { + @Suppress("DEPRECATION") + onScreenshot( + id = metadata.id, + name = metadata.name, + group = metadata.group, + styleName = metadata.styleName, + tags = metadata.tags, + extraMetadata = metadata.extraMetadata, + screenshotType = metadata.screenshotType, + screenshotBitmap = metadata.screenshotBitmap, + ) + } + /** + * Legacy 8-parameter overload kept for backwards compatibility with subclasses written against + * the pre-refactor signature. New implementations should override [onScreenshot] above instead. + */ + @Suppress("LongParameterList") + @Deprecated( + message = "Override the single-parameter onScreenshot(ScreenshotMetadata) instead.", + replaceWith = ReplaceWith("onScreenshot(metadata)"), + level = DeprecationLevel.WARNING, + ) fun onScreenshot( id: String, name: String, @@ -91,11 +90,15 @@ interface ShowkaseScreenshotTest { extraMetadata: List = emptyList(), screenshotType: ShowkaseScreenshotType, screenshotBitmap: Bitmap, - ) + ) { + throw NotImplementedError( + "ShowkaseScreenshotTest implementations must override onScreenshot(ScreenshotMetadata)." + ) + } @RequiresApi(Build.VERSION_CODES.O) fun takeComposableScreenshot( - showkaseBrowserComponent: ShowkaseBrowserComponent + showkaseBrowserComponent: ShowkaseBrowserComponent, ) { try { // Disable animations for screenshots to make them deterministic @@ -103,29 +106,30 @@ interface ShowkaseScreenshotTest { composeTestRule.setContent { showkaseBrowserComponent.component() } val bitmap = composeTestRule.onRoot().captureToImage().asAndroidBitmap() onScreenshot( - id = showkaseBrowserComponent.componentKey, - name = showkaseBrowserComponent.componentName, - group = showkaseBrowserComponent.group, - styleName = showkaseBrowserComponent.styleName, - tags = showkaseBrowserComponent.tags, - extraMetadata = showkaseBrowserComponent.extraMetadata, - screenshotType = ShowkaseScreenshotType.Composable, - screenshotBitmap = bitmap, + ScreenshotMetadata( + id = showkaseBrowserComponent.componentKey, + name = showkaseBrowserComponent.componentName, + group = showkaseBrowserComponent.group, + styleName = showkaseBrowserComponent.styleName, + tags = showkaseBrowserComponent.tags, + extraMetadata = showkaseBrowserComponent.extraMetadata, + screenshotType = ShowkaseScreenshotType.Composable, + screenshotBitmap = bitmap, + ) ) } catch (e: Throwable) { throw RuntimeException( "Failure while screenshotting component $showkaseBrowserComponent", - e + e, ) } } @RequiresApi(Build.VERSION_CODES.O) fun takeTypographyScreenshot( - showkaseBrowserTypography: ShowkaseBrowserTypography + showkaseBrowserTypography: ShowkaseBrowserTypography, ) { try { - // Disable animations for screenshots to make them deterministic composeTestRule.mainClock.autoAdvance = false composeTestRule.setContent { BasicText( @@ -134,32 +138,33 @@ interface ShowkaseScreenshotTest { }, modifier = Modifier .fillMaxWidth() - .padding(padding4x), - style = showkaseBrowserTypography.textStyle + .padding(componentPadding), + style = showkaseBrowserTypography.textStyle, ) } val bitmap = composeTestRule.onRoot().captureToImage().asAndroidBitmap() onScreenshot( - id = showkaseBrowserTypography.hashCode().toString(), - name = showkaseBrowserTypography.typographyName, - group = showkaseBrowserTypography.typographyGroup, - screenshotType = ShowkaseScreenshotType.Typography, - screenshotBitmap = bitmap, + ScreenshotMetadata( + id = "${showkaseBrowserTypography.typographyGroup}_${showkaseBrowserTypography.typographyName}", + name = showkaseBrowserTypography.typographyName, + group = showkaseBrowserTypography.typographyGroup, + screenshotType = ShowkaseScreenshotType.Typography, + screenshotBitmap = bitmap, + ) ) } catch (e: Throwable) { throw RuntimeException( - "Failure while screenshotting typogrpahy $showkaseBrowserTypography", - e + "Failure while screenshotting typography $showkaseBrowserTypography", + e, ) } } @RequiresApi(Build.VERSION_CODES.O) fun takeColorScreenshot( - showkaseBrowserColor: ShowkaseBrowserColor + showkaseBrowserColor: ShowkaseBrowserColor, ) { try { - // Disable animations for screenshots to make them deterministic composeTestRule.mainClock.autoAdvance = false composeTestRule.setContent { Box( @@ -171,11 +176,13 @@ interface ShowkaseScreenshotTest { } val bitmap = composeTestRule.onRoot().captureToImage().asAndroidBitmap() onScreenshot( - id = showkaseBrowserColor.hashCode().toString(), - name = showkaseBrowserColor.colorName, - group = showkaseBrowserColor.colorGroup, - screenshotType = ShowkaseScreenshotType.Color, - screenshotBitmap = bitmap, + ScreenshotMetadata( + id = "${showkaseBrowserColor.colorGroup}_${showkaseBrowserColor.colorName}", + name = showkaseBrowserColor.colorName, + group = showkaseBrowserColor.colorGroup, + screenshotType = ShowkaseScreenshotType.Color, + screenshotBitmap = bitmap, + ) ) } catch (e: Throwable) { throw RuntimeException("Failure while screenshotting color $showkaseBrowserColor", e) diff --git a/showkase/build.gradle b/showkase/build.gradle deleted file mode 100644 index 3d2ee4f27..000000000 --- a/showkase/build.gradle +++ /dev/null @@ -1,71 +0,0 @@ -import com.vanniktech.maven.publish.AndroidMultiVariantLibrary - -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'org.jetbrains.kotlin.plugin.compose' - id 'com.vanniktech.maven.publish' -} - -android { - namespace "com.airbnb.android.showkase" - - defaultConfig { - minSdkVersion 21 - compileSdk 36 - targetSdkVersion 33 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - debug { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - kotlinOptions { - freeCompilerArgs += [ - '-Xopt-in=kotlin.RequiresOptIn', - ] - } - buildFeatures { - compose true - } - // Added to avoid this error - - // Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. - // > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction - // > 2 files found with path 'META-INF/AL2.0' from inputs: - packagingOptions { - exclude 'META-INF/AL2.0' - exclude 'META-INF/LGPL2.1' - } -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - // Showkase - api project(':showkase-annotation') - - // Support Libraries - implementation deps.support.appCompat - implementation deps.support.ktx - - // Compose - implementation deps.compose.activityCompose - implementation deps.compose.composeRuntime - implementation deps.compose.composeNavigation - implementation deps.compose.core - implementation deps.compose.foundation - implementation deps.compose.tooling - implementation deps.compose.layout - implementation deps.compose.material -} - -mavenPublishing { - configure(new AndroidMultiVariantLibrary(true, true)) -} diff --git a/showkase/build.gradle.kts b/showkase/build.gradle.kts new file mode 100644 index 000000000..fd669b985 --- /dev/null +++ b/showkase/build.gradle.kts @@ -0,0 +1,74 @@ +import com.vanniktech.maven.publish.AndroidMultiVariantLibrary +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.SourcesJar + +plugins { + id("com.android.library") + alias(libs.plugins.kotlin.compose) + id("com.vanniktech.maven.publish") +} + +android { + namespace = "com.airbnb.android.showkase" + + defaultConfig { + minSdk = 23 + compileSdk = 36 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + getByName("debug") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + buildFeatures { + compose = true + } + + packaging { + resources { + excludes += listOf("META-INF/AL2.0", "META-INF/LGPL2.1") + } + } + lint { + baseline = file("lint-baseline.xml") + } +} + +kotlin { + jvmToolchain(21) + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") + } +} + +dependencies { + api(project(":showkase-annotation")) + api(project(":showkase-models")) + + implementation(libs.support.appCompat) + implementation(libs.support.ktx) + implementation(libs.support.lifecycleComposeRuntime) + + implementation(libs.compose.activityCompose) + implementation(libs.compose.composeRuntime) + implementation(libs.compose.composeNavigation) + implementation(libs.compose.core) + implementation(libs.compose.foundation) + implementation(libs.compose.tooling) + implementation(libs.compose.layout) + implementation(libs.compose.material3) + implementation(libs.compose.materialIconsCore) + + testImplementation(libs.test.junit) + testImplementation(libs.test.googleTruth) +} + +mavenPublishing { + configure(AndroidMultiVariantLibrary(JavadocJar.Empty(), SourcesJar.Sources())) +} diff --git a/showkase/lint-baseline.xml b/showkase/lint-baseline.xml new file mode 100644 index 000000000..2f215652f --- /dev/null +++ b/showkase/lint-baseline.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/showkase/src/main/java/com/airbnb/android/showkase/exceptions/ShowkaseException.kt b/showkase/src/main/java/com/airbnb/android/showkase/exceptions/ShowkaseException.kt index 4cccc88bd..3e412be2a 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/exceptions/ShowkaseException.kt +++ b/showkase/src/main/java/com/airbnb/android/showkase/exceptions/ShowkaseException.kt @@ -1,8 +1,9 @@ package com.airbnb.android.showkase.exceptions -import java.lang.Exception - /** * Used to throw an exception for Showkase specific errors. */ -internal class ShowkaseException(message: String) : Exception(message) +internal class ShowkaseException( + message: String, + cause: Throwable? = null, +) : Exception(message, cause) diff --git a/showkase/src/main/java/com/airbnb/android/showkase/ui/BackButtonHandler.kt b/showkase/src/main/java/com/airbnb/android/showkase/ui/BackButtonHandler.kt deleted file mode 100644 index 0bf8a43b3..000000000 --- a/showkase/src/main/java/com/airbnb/android/showkase/ui/BackButtonHandler.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.airbnb.android.showkase.ui - -import android.content.ContextWrapper -import androidx.activity.ComponentActivity -import androidx.activity.OnBackPressedCallback -import androidx.activity.OnBackPressedDispatcherOwner -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.platform.LocalContext - -/** - * Related discussion - - * https://kotlinlang.slack.com/archives/CJLTWPH7S/p1591558155394500?thread_ts=1591558024.394400&cid=CJLTWPH7S - */ -private val LocalBackPressedDispatcher = staticCompositionLocalOf { null } - -private class ComposableBackHandler(enabled: Boolean) : OnBackPressedCallback(enabled) { - lateinit var onBackPressed: () -> Unit - - override fun handleOnBackPressed() { - onBackPressed() - } -} - -@Composable -internal fun Handler( - enabled: Boolean = true, - onBackPressed: () -> Unit -) { - val dispatcher = (LocalBackPressedDispatcher.current ?: return).onBackPressedDispatcher - val handler = remember { ComposableBackHandler(enabled) } - DisposableEffect(dispatcher) { - dispatcher.addCallback(handler) - onDispose { handler.remove() } - } - LaunchedEffect(enabled) { - handler.isEnabled = enabled - } - LaunchedEffect(onBackPressed) { - handler.onBackPressed = onBackPressed - } -} - -// TODO(vinaygaba) - Replace with the version Compose just exposed in the activity-compose bindings -@Composable -internal fun BackButtonHandler( - onBackPressed: () -> Unit, -) { - var context = LocalContext.current - // Inspired from https://cs.android.com/androidx/platform/frameworks/support/+/ - // androidx-master-dev:navigation/navigation-compose/src/main/java/androidx/navigation/ - // compose/NavHost.kt;l=88 - // This was necessary because using Jetpack Navigation does not allow typecasting a - // NavBackStackEntry to LifecycleOwnerAmbient. - while (context is ContextWrapper) { - if (context is OnBackPressedDispatcherOwner) { - break - } - context = context.baseContext - } - CompositionLocalProvider( - LocalBackPressedDispatcher provides context as ComponentActivity - ) { - Handler { - onBackPressed() - } - } -} diff --git a/showkase/src/main/java/com/airbnb/android/showkase/ui/CommonComponents.kt b/showkase/src/main/java/com/airbnb/android/showkase/ui/CommonComponents.kt index 932c61166..beb2d8139 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/ui/CommonComponents.kt +++ b/showkase/src/main/java/com/airbnb/android/showkase/ui/CommonComponents.kt @@ -1,37 +1,45 @@ package com.airbnb.android.showkase.ui import androidx.activity.compose.LocalOnBackPressedDispatcherOwner -import androidx.compose.material.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.Card -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.MaterialTheme -import androidx.compose.material.darkColors -import androidx.compose.material.lightColors +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp +import com.airbnb.android.showkase.R import com.airbnb.android.showkase.models.ShowkaseBrowserComponent -@OptIn(ExperimentalMaterialApi::class) +private val LightColors = lightColorScheme() +private val DarkColors = darkColorScheme() + @Composable -fun SimpleTextCard( +internal fun SimpleTextCard( text: String, - onClick: () -> Unit + onClick: () -> Unit, ) { Card( modifier = Modifier .fillMaxWidth() - .padding(start = padding4x, end = padding4x, top = padding2x, bottom = padding2x), - onClick = onClick + .padding(start = padding4x, end = padding4x, top = padding2x, bottom = padding2x) + .clickable(onClick = onClick), ) { Text( text = text, @@ -39,8 +47,8 @@ fun SimpleTextCard( style = TextStyle( fontSize = 20.sp, fontFamily = FontFamily.Serif, - fontWeight = FontWeight.Bold - ) + fontWeight = FontWeight.Bold, + ), ) } } @@ -51,13 +59,13 @@ internal fun ComponentCardTitle(componentName: String) { text = componentName, modifier = Modifier.padding( start = padding4x, end = padding4x, top = padding8x, - bottom = padding1x + bottom = padding1x, ), style = TextStyle( fontSize = 16.sp, fontFamily = FontFamily.Serif, - fontWeight = FontWeight.Bold - ) + fontWeight = FontWeight.Bold, + ), ) } @@ -74,14 +82,14 @@ internal fun ComponentCard( val composableModifier = Modifier.generateComposableModifier(metadata) val composableContainerModifier = Modifier.generateContainerModifier(onClick) MaterialTheme( - colors = if (darkMode) darkColors() else lightColors() + colorScheme = if (darkMode) DarkColors else LightColors, ) { Card( - shape = MaterialTheme.shapes.large + shape = MaterialTheme.shapes.large, ) { Box { Column(modifier = composableModifier) { - metadata.component() + DialogAwareComponent(metadata) } // Need to add this as part of the stack so that we can intercept the touch of the // component when we are on the "Group components" screen. If @@ -91,7 +99,7 @@ internal fun ComponentCard( Column( modifier = Modifier .matchParentSize() - .then(composableContainerModifier) + .then(composableContainerModifier), ) {} } } @@ -104,3 +112,20 @@ private fun Modifier.generateContainerModifier(onClick: (() -> Unit)?) = fillMaxWidth() .clickable(onClick = onClick) } ?: fillMaxWidth() + +@Composable +internal fun DialogAwareComponent(metadata: ShowkaseBrowserComponent) { + if (!metadata.isDialog) { + metadata.component() + return + } + var show by remember { mutableStateOf(false) } + val showLabel = metadata.dialogButtonText + .ifEmpty { stringResource(R.string.showkase_browser_show_dialog) } + val hideLabel = metadata.dialogHideButtonText + .ifEmpty { stringResource(R.string.showkase_browser_hide_dialog) } + Button(onClick = { show = !show }) { + Text(text = if (show) hideLabel else showLabel) + } + if (show) metadata.component() +} diff --git a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserActivity.kt b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserActivity.kt index b96ecda4f..bc41f2c3d 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserActivity.kt +++ b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserActivity.kt @@ -66,18 +66,24 @@ class ShowkaseBrowserActivity : AppCompatActivity() { private fun getShowkaseProviderElements( classKey: String - ): ShowkaseElementsMetadata { - return try { - val showkaseComponentProvider = - Class.forName("$classKey$AUTOGEN_CLASS_NAME").getDeclaredConstructor().newInstance() - val showkaseMetadata = (showkaseComponentProvider as ShowkaseProvider).metadata() - ShowkaseElementsMetadata( - componentList = showkaseMetadata.componentList, - colorList = showkaseMetadata.colorList, - typographyList = showkaseMetadata.typographyList - ) - } catch (exception: ClassNotFoundException) { - ShowkaseElementsMetadata() + ): ShowkaseElementsMetadata = runCatching { + val showkaseComponentProvider = + Class.forName("$classKey$AUTOGEN_CLASS_NAME").getDeclaredConstructor().newInstance() + (showkaseComponentProvider as ShowkaseProvider).metadata() + }.getOrElse { e -> + when (e) { + is ClassNotFoundException -> ShowkaseElementsMetadata() + is ReflectiveOperationException, + is ClassCastException, + is LinkageError -> + throw ShowkaseException( + "Failed to load generated Showkase provider for $classKey$AUTOGEN_CLASS_NAME. " + + "This usually means the processor didn't run on the module that contains your " + + "@ShowkaseRoot, or the generated class is out of sync with the runtime. " + + "Rebuild the project and verify your @ShowkaseRoot setup.", + cause = e, + ) + else -> throw e } } diff --git a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserApp.kt b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserApp.kt index 0dfc3725e..7a874f8b5 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserApp.kt +++ b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseBrowserApp.kt @@ -14,18 +14,19 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.Scaffold -import androidx.compose.material.Surface -import androidx.compose.material.Text -import androidx.compose.material.TextField -import androidx.compose.material.TextFieldDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Search import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -80,12 +81,13 @@ internal fun ShowkaseBrowserApp( ) { val navController = rememberNavController() val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentRoute = navBackStackEntry?.destination?.route + val currentRoute by remember(navBackStackEntry) { + derivedStateOf { navBackStackEntry?.destination?.route } + } Surface( color = Color.White ) { Scaffold( - drawerContent = null, topBar = { ShowkaseAppBar( currentRoute = currentRoute, @@ -151,7 +153,7 @@ internal fun ShowkaseAppBar( onClearSearch: () -> Unit, ) { Surface( - elevation = 4.dp + shadowElevation = 4.dp ) { Row( Modifier @@ -188,45 +190,6 @@ internal fun ShowkaseAppBar( ) } } - - /** - * Commented out due to TopAppBar not working properly in beta-01 for this use case. Seems to be - * related to use to Surface inside TopAppBar and a TextField. Creating my own implementation - * for now. Will uncomment if this issue gets fixed. - */ -// TopAppBar( -// title = { -// ShowkaseAppBarTitle( -// isSearchActive = showkaseBrowserScreenMetadata.isSearchActive, -// currentGroup = showkaseBrowserScreenMetadata.currentGroup, -// currentComponentName = showkaseBrowserScreenMetadata.currentComponentName, -// currentComponentStyleName = showkaseBrowserScreenMetadata.currentComponentStyleName, -// currentRoute = currentRoute, -// searchQuery = showkaseBrowserScreenMetadata.searchQuery, -// searchQueryValueChange = { -// onSearchQueryChanged(it) -// }, -// modifier = Modifier, -// onCloseSearchFieldClick = { -// onCloseSearch() -// }, -// onClearSearchField = { -// onClearSearch() -// } -// ) -// }, -// actions = { -// ShowkaseAppBarActions( -// isActive = showkaseBrowserScreenMetadata.isSearchActive, -// onActionClicked = { -// onActivateSearch() -// }, -// currentRoute = currentRoute, -// modifier = Modifier -// ) -// }, -// backgroundColor = Color.White -// ) } @Suppress("LongParameterList") @@ -316,7 +279,7 @@ private fun AppBarTitle( } @Composable -fun ToolbarTitle( +private fun ToolbarTitle( string: String, modifier: Modifier ) { @@ -387,7 +350,7 @@ internal fun ShowkaseSearchField( Icon(imageVector = Icons.Filled.Search, contentDescription = "Search Icon") } }, - colors = TextFieldDefaults.textFieldColors(), + colors = TextFieldDefaults.colors(), trailingIcon = { IconButton( onClick = { @@ -441,11 +404,13 @@ internal fun ShowkaseBodyContent( onUpdateShowkaseBrowserScreenMetadata: (ShowkaseBrowserScreenMetadata) -> Unit, navigateTo: (ShowkaseCurrentScreen) -> Unit, ) { - val startDestination = startDestination( - groupedColorsMap, - groupedTypographyMap, - groupedComponentMap - ) + val startDestination = remember(groupedColorsMap, groupedTypographyMap, groupedComponentMap) { + startDestination( + groupedColorsMap, + groupedTypographyMap, + groupedComponentMap, + ) + } NavHost( navController = navController, startDestination = startDestination, @@ -463,7 +428,7 @@ internal fun ShowkaseBodyContent( ) } -private fun startDestination( +internal fun startDestination( groupedColorsMap: Map>, groupedTypographyMap: Map>, groupedComponentMap: Map> @@ -529,7 +494,7 @@ private fun NavGraphBuilder.navGraph( ) } -private fun Map>.isOnlyCategory( +internal fun Map>.isOnlyCategory( otherCategoryMap1: Map>, otherCategoryMap2: Map> ) = this.values.isNotEmpty() && otherCategoryMap1.isEmpty() && otherCategoryMap2.isEmpty() diff --git a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseCategoriesScreen.kt b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseCategoriesScreen.kt index b9f2b7ce4..f668ed2d5 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseCategoriesScreen.kt +++ b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseCategoriesScreen.kt @@ -1,6 +1,5 @@ package com.airbnb.android.showkase.ui -import android.util.Log import androidx.activity.compose.BackHandler import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.lazy.LazyColumn @@ -26,6 +25,7 @@ internal fun ShowkaseCategoriesScreen( LazyColumn { items( items = categoryMetadataMap.entries.toList(), + key = { it.key.name }, itemContent = { (category, categorySize) -> val defaultLocale = Locale.getDefault() val title = category.name @@ -79,17 +79,11 @@ internal fun goBackToCategoriesScreen( onBackPressOnRoot: () -> Unit, ) { when { - showkaseBrowserScreenMetadata.isSearchActive -> { - Log.e("BackPressed", "isSearchActive") + showkaseBrowserScreenMetadata.isSearchActive -> onUpdateShowkaseBrowserScreenMetadata(showkaseBrowserScreenMetadata.clearActiveSearch()) - } - onRootScreen -> { - Log.e("BackPressed", "onRootScreen") - onBackPressOnRoot() - } + onRootScreen -> onBackPressOnRoot() else -> { - Log.e("BackPressed", "else") - showkaseBrowserScreenMetadata.clear() + onUpdateShowkaseBrowserScreenMetadata(showkaseBrowserScreenMetadata.clear()) onBackToCategories() } } diff --git a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseColorsInAGroupScreen.kt b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseColorsInAGroupScreen.kt index 5583ba56b..db1169063 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseColorsInAGroupScreen.kt +++ b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseColorsInAGroupScreen.kt @@ -9,8 +9,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.Card -import androidx.compose.material.Text +import androidx.compose.material3.Card +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -54,7 +54,9 @@ internal fun ShowkaseColorsInAGroupScreen( LazyColumn( modifier = Modifier.testTag("ColorsInAGroupList") ) { - items(items = filteredList, + items( + items = filteredList, + key = { "${it.colorGroup}_${it.colorName}" }, itemContent = { groupColorMetadata -> Card( modifier = Modifier.padding( diff --git a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentDetailScreen.kt b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentDetailScreen.kt index 48f336402..6c76af02e 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentDetailScreen.kt +++ b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentDetailScreen.kt @@ -18,14 +18,14 @@ import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.Card -import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideTextStyle -import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material3.Card +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -40,7 +40,6 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily @@ -51,6 +50,7 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner import com.airbnb.android.showkase.R import com.airbnb.android.showkase.models.ShowkaseBrowserComponent import com.airbnb.android.showkase.models.ShowkaseBrowserScreenMetadata @@ -137,10 +137,10 @@ private fun DocumentationPanel(kDoc: String) { horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - ProvideTextStyle(value = MaterialTheme.typography.button) { + CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.labelLarge) { Text( text = buttonText, - color = MaterialTheme.colors.primary + color = MaterialTheme.colorScheme.primary, ) } Icon(imageVector = icon, contentDescription = buttonText) @@ -194,7 +194,7 @@ private fun RTLComponentCard(metadata: ShowkaseBrowserComponent) { Card(modifier = Modifier.fillMaxWidth()) { CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { Column(modifier = updatedModifier) { - metadata.component() + DialogAwareComponent(metadata) } } } @@ -220,14 +220,16 @@ internal fun Modifier.generateComposableModifier(metadata: ShowkaseBrowserCompon this .padding(padding4x) .sizeIn(maxHeight = Dp(LocalConfiguration.current.screenHeightDp.toFloat())) + val widthDp = metadata.widthDp + val heightDp = metadata.heightDp when { - metadata.heightDp != null && metadata.widthDp != null -> baseModifier.size( - width = metadata.widthDp.dp, - height = metadata.heightDp.dp + heightDp != null && widthDp != null -> baseModifier.size( + width = widthDp.dp, + height = heightDp.dp ) - metadata.heightDp != null -> baseModifier.height(Dp(metadata.heightDp.toFloat())) - metadata.widthDp != null -> baseModifier.width(Dp(metadata.widthDp.toFloat())) + heightDp != null -> baseModifier.height(Dp(heightDp.toFloat())) + widthDp != null -> baseModifier.width(Dp(widthDp.toFloat())) else -> baseModifier.fillMaxWidth() } } @@ -240,6 +242,13 @@ private fun back( navigateTo(ShowkaseCurrentScreen.COMPONENT_STYLES) } +/** + * Returns an [OnBackPressedDispatcherOwner] backed by a fresh, unwired + * [OnBackPressedDispatcher]. The dispatcher is intentionally not connected to the host activity's + * lifecycle — its purpose is to **swallow** back-press handlers registered by preview composables + * so they don't intercept browser-level back navigation. Provided through + * [LocalOnBackPressedDispatcherOwner] in places like [ComponentCard] and the Roborazzi wrapper. + */ @Composable internal fun rememberOnBackPressedDispatcherOwner(): OnBackPressedDispatcherOwner { val lifecycleOwner = LocalLifecycleOwner.current diff --git a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentStylesScreen.kt b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentStylesScreen.kt index 35eca5b88..01537acf0 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentStylesScreen.kt +++ b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentStylesScreen.kt @@ -37,6 +37,7 @@ internal fun ShowkaseComponentStylesScreen( LazyColumn { items( items = filteredList, + key = { it.componentKey }, itemContent = { groupComponent -> val styleName = generatedStyleName(groupComponent.styleName, componentStylesList.size) @@ -85,7 +86,7 @@ private fun back( ) { val isSearchActive = showkaseBrowserScreenMetadata.isSearchActive when { - isSearchActive -> showkaseBrowserScreenMetadata.clearActiveSearch() + isSearchActive -> onUpdateShowkaseBrowserScreenMetadata(showkaseBrowserScreenMetadata.clearActiveSearch()) else -> { onUpdateShowkaseBrowserScreenMetadata( showkaseBrowserScreenMetadata.copy( diff --git a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentsInAGroupScreen.kt b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentsInAGroupScreen.kt index 354812267..41277401a 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentsInAGroupScreen.kt +++ b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseComponentsInAGroupScreen.kt @@ -41,6 +41,7 @@ internal fun ShowkaseComponentsInAGroupScreen( LazyColumn { items( items = filteredList, + key = { it.componentKey }, itemContent = { groupComponent -> ComponentCardTitle(groupComponent.componentName) ComponentCard( @@ -78,7 +79,7 @@ private fun goBackFromComponentsInAGroupScreen( when { isSearchActive -> onUpdateShowkaseBrowserScreenMetadata(showkaseBrowserScreenMetadata.clearActiveSearch()) else -> { - showkaseBrowserScreenMetadata.clear() + onUpdateShowkaseBrowserScreenMetadata(showkaseBrowserScreenMetadata.clear()) navigateTo(ShowkaseCurrentScreen.COMPONENT_GROUPS) } } diff --git a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseErrorScreen.kt b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseErrorScreen.kt index d7874fd1f..848d47523 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseErrorScreen.kt +++ b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseErrorScreen.kt @@ -2,12 +2,12 @@ package com.airbnb.android.showkase.ui import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding -import androidx.compose.material.Snackbar +import androidx.compose.material3.Snackbar @Composable internal fun ShowkaseErrorScreen(errorText: String) { diff --git a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseGroupsScreen.kt b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseGroupsScreen.kt index a7942623d..1343b13e2 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseGroupsScreen.kt +++ b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseGroupsScreen.kt @@ -5,6 +5,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import com.airbnb.android.showkase.models.ShowkaseBrowserColor @@ -37,6 +38,7 @@ internal fun ShowkaseGroupsScreen( LazyColumn { items( items = filteredMap.entries.toList(), + key = { it.key }, itemContent = { (group, list) -> val size = getNumOfUIElements(list) SimpleTextCard( @@ -146,13 +148,15 @@ internal fun ShowkaseTypographyGroupsScreen( onUpdateShowkaseBrowserScreenMetadata: (ShowkaseBrowserScreenMetadata) -> Unit, navigateTo: (ShowkaseCurrentScreen) -> Unit, ) { - if (groupedTypographyMap.size == 1) { - onUpdateShowkaseBrowserScreenMetadata( - showkaseBrowserScreenMetadata.copy( - currentGroup = groupedTypographyMap.entries.first().key, + val singleGroup = groupedTypographyMap.entries.singleOrNull() + LaunchedEffect(singleGroup) { + if (singleGroup != null && showkaseBrowserScreenMetadata.currentGroup != singleGroup.key) { + onUpdateShowkaseBrowserScreenMetadata( + showkaseBrowserScreenMetadata.copy(currentGroup = singleGroup.key) ) - ) - + } + } + if (singleGroup != null) { ShowkaseTypographyInAGroupScreen( groupedTypographyMap = groupedTypographyMap, showkaseBrowserScreenMetadata = showkaseBrowserScreenMetadata, diff --git a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseTypographyInAGroupScreen.kt b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseTypographyInAGroupScreen.kt index 0b03423e8..391d73c50 100644 --- a/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseTypographyInAGroupScreen.kt +++ b/showkase/src/main/java/com/airbnb/android/showkase/ui/ShowkaseTypographyInAGroupScreen.kt @@ -7,8 +7,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.Divider -import androidx.compose.material.Text +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -50,6 +50,7 @@ internal fun ShowkaseTypographyInAGroupScreen( ) { items( items = filteredList, + key = { "${it.typographyGroup}_${it.typographyName}" }, itemContent = { groupTypographyMetadata -> Text( text = groupTypographyMetadata.typographyName.replaceFirstChar { @@ -60,7 +61,7 @@ internal fun ShowkaseTypographyInAGroupScreen( .padding(padding4x), style = groupTypographyMetadata.textStyle ) - Divider() + HorizontalDivider() } ) } diff --git a/showkase/src/main/res/values/strings.xml b/showkase/src/main/res/values/strings.xml index c8af80936..56407c111 100644 --- a/showkase/src/main/res/values/strings.xml +++ b/showkase/src/main/res/values/strings.xml @@ -4,6 +4,8 @@ Search Show Documentation Hide Documentation + Show Dialog + Hide Dialog Components Colors Typography diff --git a/showkase/src/test/java/com/airbnb/android/showkase/models/ShowkaseBrowserScreenMetadataTest.kt b/showkase/src/test/java/com/airbnb/android/showkase/models/ShowkaseBrowserScreenMetadataTest.kt new file mode 100644 index 000000000..0f6ed5e3d --- /dev/null +++ b/showkase/src/test/java/com/airbnb/android/showkase/models/ShowkaseBrowserScreenMetadataTest.kt @@ -0,0 +1,78 @@ +package com.airbnb.android.showkase.models + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +/** + * Covers the `clear()` and `clearActiveSearch()` extension functions on + * [ShowkaseBrowserScreenMetadata]. These are the helpers behind the three previously-broken + * back-press state-reset bugs — the tests here guard against regressions. + */ +class ShowkaseBrowserScreenMetadataTest { + + @Test + fun `clear resets every field on the metadata`() { + val populated = ShowkaseBrowserScreenMetadata( + currentGroup = "G", + currentComponentName = "Comp", + currentComponentStyleName = "Style", + currentComponentKey = "G-Comp-Style", + isSearchActive = true, + searchQuery = "needle", + ) + + val cleared = populated.clear() + + assertThat(cleared.currentGroup).isNull() + assertThat(cleared.currentComponentName).isNull() + assertThat(cleared.currentComponentStyleName).isNull() + assertThat(cleared.currentComponentKey).isNull() + assertThat(cleared.isSearchActive).isFalse() + assertThat(cleared.searchQuery).isNull() + } + + @Test + fun `clearActiveSearch only resets isSearchActive and searchQuery`() { + val populated = ShowkaseBrowserScreenMetadata( + currentGroup = "G", + currentComponentName = "Comp", + currentComponentStyleName = "Style", + currentComponentKey = "G-Comp-Style", + isSearchActive = true, + searchQuery = "needle", + ) + + val cleared = populated.clearActiveSearch() + + assertThat(cleared.currentGroup).isEqualTo("G") + assertThat(cleared.currentComponentName).isEqualTo("Comp") + assertThat(cleared.currentComponentStyleName).isEqualTo("Style") + assertThat(cleared.currentComponentKey).isEqualTo("G-Comp-Style") + assertThat(cleared.isSearchActive).isFalse() + assertThat(cleared.searchQuery).isNull() + } + + @Test + fun `clear returns a new instance and does not mutate the original`() { + val original = ShowkaseBrowserScreenMetadata(currentGroup = "G", searchQuery = "q") + val cleared = original.clear() + assertThat(original.currentGroup).isEqualTo("G") + assertThat(original.searchQuery).isEqualTo("q") + assertThat(cleared).isNotEqualTo(original) + } + + @Test + fun `insideGroup is true for the three in-group routes`() { + assertThat(ShowkaseCurrentScreen.COMPONENTS_IN_A_GROUP.name.insideGroup()).isTrue() + assertThat(ShowkaseCurrentScreen.COLORS_IN_A_GROUP.name.insideGroup()).isTrue() + assertThat(ShowkaseCurrentScreen.TYPOGRAPHY_IN_A_GROUP.name.insideGroup()).isTrue() + } + + @Test + fun `insideGroup is false for other routes and null`() { + assertThat(ShowkaseCurrentScreen.SHOWKASE_CATEGORIES.name.insideGroup()).isFalse() + assertThat(ShowkaseCurrentScreen.COMPONENT_GROUPS.name.insideGroup()).isFalse() + assertThat(ShowkaseCurrentScreen.COMPONENT_DETAIL.name.insideGroup()).isFalse() + assertThat((null as String?).insideGroup()).isFalse() + } +} diff --git a/showkase/src/test/java/com/airbnb/android/showkase/ui/ShowkaseFilterTest.kt b/showkase/src/test/java/com/airbnb/android/showkase/ui/ShowkaseFilterTest.kt new file mode 100644 index 000000000..fbc11d7e4 --- /dev/null +++ b/showkase/src/test/java/com/airbnb/android/showkase/ui/ShowkaseFilterTest.kt @@ -0,0 +1,138 @@ +package com.airbnb.android.showkase.ui + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import com.airbnb.android.showkase.models.ShowkaseBrowserColor +import com.airbnb.android.showkase.models.ShowkaseBrowserComponent +import com.airbnb.android.showkase.models.ShowkaseBrowserTypography +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +/** + * Unit tests for the pure-function filter and helper utilities in the showkase browser. These + * cover [matchSearchQuery], [getNumOfUIElements], and the various `getFilteredSearchList` helpers + * across the UI screens — all of which previously had zero coverage. + */ +class ShowkaseFilterTest { + + private fun component(name: String, group: String = "G", style: String? = null) = + ShowkaseBrowserComponent( + componentKey = "$group-$name-${style ?: ""}", + group = group, + componentName = name, + componentKDoc = "", + component = {}, + styleName = style, + ) + + private fun color(name: String, group: String = "G") = + ShowkaseBrowserColor(group, name, "", Color.Red) + + private fun typography(name: String, group: String = "G") = + ShowkaseBrowserTypography(group, name, "", TextStyle()) + + // matchSearchQuery — defined in ShowkaseComponentStylesScreen.kt:122-125 + @Test + fun `matchSearchQuery finds substring match in single property`() { + assertThat(matchSearchQuery("alpha", "AlphaButton")).isTrue() + } + + @Test + fun `matchSearchQuery is case insensitive`() { + assertThat(matchSearchQuery("ALPHA", "alphabutton")).isTrue() + assertThat(matchSearchQuery("alpha", "AlphaButton")).isTrue() + } + + @Test + fun `matchSearchQuery searches all provided properties`() { + assertThat(matchSearchQuery("dark", "PrimaryButton", "Dark theme variant")).isTrue() + assertThat(matchSearchQuery("missing", "PrimaryButton", "Dark theme variant")).isFalse() + } + + @Test + fun `matchSearchQuery returns false when no property matches`() { + assertThat(matchSearchQuery("zzz", "AlphaButton", "BetaCard")).isFalse() + } + + @Test + fun `matchSearchQuery treats empty query as matching any property containing empty string`() { + // String.contains("") is true for every non-null string — this captures intentional behavior. + assertThat(matchSearchQuery("", "anything")).isTrue() + } + + // getNumOfUIElements — defined in ShowkaseGroupsScreen.kt:73-79 + @Test + fun `getNumOfUIElements dedupes components by componentName`() { + val list = listOf( + component("Button", style = "light"), + component("Button", style = "dark"), + component("Card"), + ) + // Two unique component names: Button, Card. + assertThat(getNumOfUIElements(list)).isEqualTo(2) + } + + @Test + fun `getNumOfUIElements returns raw size for non-component lists`() { + val colors = listOf(color("Red"), color("Blue"), color("Green")) + assertThat(getNumOfUIElements(colors)).isEqualTo(3) + } + + @Test + fun `getNumOfUIElements returns 0 for empty list`() { + assertThat(getNumOfUIElements(emptyList())).isEqualTo(0) + } + + // getFilteredSearchList (generic Map variant) — defined in ShowkaseGroupsScreen.kt:81-97 + @Test + fun `getFilteredSearchList passes through map when search inactive`() { + val map = mapOf("a" to listOf(1), "b" to listOf(2, 3)) + val result = getFilteredSearchList(map, isSearchActive = false, searchQuery = "any") + assertThat(result).isEqualTo(map) + } + + @Test + fun `getFilteredSearchList filters by key when search active with query`() { + val map = mapOf("alpha" to listOf(1), "beta" to listOf(2), "gamma" to listOf(3)) + val result = getFilteredSearchList(map, isSearchActive = true, searchQuery = "et") + assertThat(result.keys).containsExactly("beta") + } + + @Test + fun `getFilteredSearchList passes through when search active but query blank`() { + val map = mapOf("alpha" to listOf(1), "beta" to listOf(2)) + val result = getFilteredSearchList(map, isSearchActive = true, searchQuery = "") + assertThat(result).isEqualTo(map) + } + + @Test + fun `getFilteredSearchList passes through when search active but query null`() { + val map = mapOf("alpha" to listOf(1), "beta" to listOf(2)) + val result = getFilteredSearchList(map, isSearchActive = true, searchQuery = null) + assertThat(result).isEqualTo(map) + } + + // isOnlyCategory — defined in ShowkaseBrowserApp.kt:532-535 + @Test + fun `isOnlyCategory is true when receiver has entries and others are empty`() { + val components = mapOf("g" to listOf(component("c"))) + val emptyColors = emptyMap>() + val emptyTypo = emptyMap>() + assertThat(components.isOnlyCategory(emptyColors, emptyTypo)).isTrue() + } + + @Test + fun `isOnlyCategory is false when receiver is empty`() { + val empty = emptyMap>() + val nonEmpty = mapOf("g" to listOf(1)) + assertThat(empty.isOnlyCategory(nonEmpty, nonEmpty)).isFalse() + } + + @Test + fun `isOnlyCategory is false when another category is non-empty`() { + val components = mapOf("g" to listOf(component("c"))) + val nonEmptyColors = mapOf("g" to listOf(color("c"))) + val emptyTypo = emptyMap>() + assertThat(components.isOnlyCategory(nonEmptyColors, emptyTypo)).isFalse() + } +} diff --git a/showkase/src/test/java/com/airbnb/android/showkase/ui/ShowkaseStartDestinationTest.kt b/showkase/src/test/java/com/airbnb/android/showkase/ui/ShowkaseStartDestinationTest.kt new file mode 100644 index 000000000..3c451685a --- /dev/null +++ b/showkase/src/test/java/com/airbnb/android/showkase/ui/ShowkaseStartDestinationTest.kt @@ -0,0 +1,65 @@ +package com.airbnb.android.showkase.ui + +import com.airbnb.android.showkase.models.ShowkaseBrowserColor +import com.airbnb.android.showkase.models.ShowkaseBrowserComponent +import com.airbnb.android.showkase.models.ShowkaseBrowserTypography +import com.airbnb.android.showkase.models.ShowkaseCurrentScreen +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle + +/** + * Covers the decision logic in [startDestination] (`ShowkaseBrowserApp.kt:466-482`) — when only + * one category has entries we jump straight to its groups screen; otherwise we land on the + * categories selector. + */ +class ShowkaseStartDestinationTest { + + private val componentMap = mapOf( + "g" to listOf( + ShowkaseBrowserComponent( + componentKey = "g-c", + group = "g", + componentName = "c", + componentKDoc = "", + component = {}, + ) + ) + ) + private val colorMap = mapOf("g" to listOf(ShowkaseBrowserColor("g", "red", "", Color.Red))) + private val typoMap = mapOf("g" to listOf(ShowkaseBrowserTypography("g", "h1", "", TextStyle()))) + private val emptyComponentMap = emptyMap>() + private val emptyColorMap = emptyMap>() + private val emptyTypoMap = emptyMap>() + + @Test + fun `start destination is COMPONENT_GROUPS when only components are present`() { + assertThat(startDestination(emptyColorMap, emptyTypoMap, componentMap)) + .isEqualTo(ShowkaseCurrentScreen.COMPONENT_GROUPS.name) + } + + @Test + fun `start destination is COLOR_GROUPS when only colors are present`() { + assertThat(startDestination(colorMap, emptyTypoMap, emptyComponentMap)) + .isEqualTo(ShowkaseCurrentScreen.COLOR_GROUPS.name) + } + + @Test + fun `start destination is TYPOGRAPHY_GROUPS when only typography is present`() { + assertThat(startDestination(emptyColorMap, typoMap, emptyComponentMap)) + .isEqualTo(ShowkaseCurrentScreen.TYPOGRAPHY_GROUPS.name) + } + + @Test + fun `start destination is SHOWKASE_CATEGORIES when multiple categories are present`() { + assertThat(startDestination(colorMap, typoMap, componentMap)) + .isEqualTo(ShowkaseCurrentScreen.SHOWKASE_CATEGORIES.name) + } + + @Test + fun `start destination is SHOWKASE_CATEGORIES when all categories empty`() { + assertThat(startDestination(emptyColorMap, emptyTypoMap, emptyComponentMap)) + .isEqualTo(ShowkaseCurrentScreen.SHOWKASE_CATEGORIES.name) + } +}