-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 4b9092b
Showing
18 changed files
with
10,148 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# DappyKit + Farcaster + Vercel Template | ||
|
||
This is a Quiz Typescript Frame template for Farcaster integrated with DappyKit. The template is ready to deploy on Vercel. A KV storage must be created to store user sessions for the project to work. | ||
|
||
In the `./quiz.json` file, a list of questions and answers is stored. | ||
The project is configured in Vercel through environment variables. Here is the list of variables: | ||
- `APP_OWNER_FID` - a unique user number in Farcaster, the application administrator. | ||
- `APP_PK` - an ETH-compatible private key in 0x format for signing messages. | ||
- `APP_SHARE_URL` - the link to the Frame itself after deployment, needed to share the Quiz with other users. | ||
- `AUTH_SERVICE_ADDRESS` - **not required**. Auth signer address from https://github.com/DappyKit/farcaster-auth. | ||
- `APP_AUTH_URL` - **not required**. The link to the authorization Frame. You can provide your own if the standard one doesn't fit. | ||
- `APP_TITLE` - **not required**. The title of your application. | ||
- `KV_NAMESPACE` - **not required**. A unique namespace for your application. Required only if multiple applications use the same KV storage. | ||
|
||
## Video Tutorial | ||
|
||
[](https://www.youtube.com/watch?v=KMyvM20NDx8) | ||
|
||
## Create your custom Quiz with ChatGPT | ||
|
||
🆓 Use this [prompt](./GPT_PROMPT.md). | ||
|
||
🆓 If you are an **OLD** free ChatGPT user, you can generate a correct `quiz.json` file using this chat: https://chatgpt.com/g/g-UFjeBIRpE-dappykit-quiz-for-farcaster | ||
|
||
💸 If you have a ChatGPT subscription, you can generate the entire project with the quiz in this chat: https://chatgpt.com/g/g-mxV7W9Rgi-dappykit-quiz-frame-for-farcaster | ||
|
||
To generate the quiz, you need to specify the topic of the questions, and ChatGPT will do the rest for you. You can also specify the number of questions you need to create. By default, the chat will generate 30 questions. | ||
|
||
|
||
## Installation | ||
``` | ||
npm ci | ||
npm run dev | ||
``` | ||
|
||
Go to http://localhost:5173/api/dev |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { Quiz, QuizStructure, Question, CheckResult } from '../api/quiz/index.ts' | ||
|
||
function generateQuestion(correctAnswerIndex: number): Question { | ||
const numAnswers = Quiz.expectedAnswersCount | ||
const question: Question = { | ||
question: 'Random question?', | ||
answers: [], | ||
correctAnswerIndex: correctAnswerIndex, | ||
} | ||
|
||
// Generate random answers | ||
for (let i = 0; i < numAnswers; i++) { | ||
question.answers.push(`Answer ${i + 1}`) | ||
} | ||
|
||
return question | ||
} | ||
|
||
describe('Quiz', () => { | ||
let quizStructure: QuizStructure | ||
|
||
beforeEach(() => { | ||
quizStructure = { | ||
questions: [ | ||
generateQuestion(2), // Correct answer at index 2 | ||
generateQuestion(1), // Correct answer at index 1 | ||
], | ||
} | ||
}) | ||
|
||
it('should validate quiz structure', () => { | ||
expect(Quiz.validateQuizStructure(quizStructure)).toBe(true) | ||
}) | ||
|
||
it('should check correct answer', () => { | ||
const quiz = new Quiz(quizStructure) | ||
const result: CheckResult = quiz.check(2) | ||
expect(result.isCorrect).toBe(true) // Correct answer for first question | ||
expect(result.points).toBe(1) | ||
expect(result.nextQuestionId).toBe(1) | ||
}) | ||
|
||
it('should check incorrect answer', () => { | ||
const quiz = new Quiz(quizStructure) | ||
const result: CheckResult = quiz.check(1) | ||
expect(result.isCorrect).toBe(false) // Incorrect answer for first question | ||
expect(result.points).toBe(0) | ||
expect(result.nextQuestionId).toBe(1) | ||
}) | ||
|
||
it('should handle last question', () => { | ||
const quiz = new Quiz(quizStructure, 1) // Start with second question | ||
const result: CheckResult = quiz.check(1) | ||
expect(result.isCorrect).toBe(true) // Correct answer for second question | ||
expect(result.points).toBe(1) | ||
expect(result.nextQuestionId).toBeNull() | ||
}) | ||
|
||
it('should return false for invalid quiz structure', () => { | ||
const invalidQuizStructure: QuizStructure = { | ||
questions: [ | ||
generateQuestion(1), // Correct answer at index 1 | ||
], | ||
} | ||
invalidQuizStructure.questions[0].answers.pop() // make the number of answers less than expected | ||
expect(Quiz.validateQuizStructure(invalidQuizStructure)).toBe(false) | ||
}) | ||
|
||
it('should handle empty quiz structure', () => { | ||
const emptyQuizStructure: QuizStructure = { questions: [] } | ||
expect(() => new Quiz(emptyQuizStructure)).toThrow() | ||
}) | ||
|
||
it('should not accept answer index out of bounds', () => { | ||
const quiz = new Quiz(quizStructure) | ||
const result: CheckResult = quiz.check(10) | ||
expect(result.isCorrect).toBe(false) | ||
expect(result.points).toBe(0) | ||
expect(result.nextQuestionId).toBe(1) | ||
}) | ||
|
||
it('should handle questions with the same answers', () => { | ||
const sameAnswersQuizStructure: QuizStructure = { | ||
questions: [ | ||
{ | ||
question: 'Which one?', | ||
answers: ['A', 'A', 'A'], | ||
correctAnswerIndex: 1, | ||
}, | ||
], | ||
} | ||
const quiz = new Quiz(sameAnswersQuizStructure) | ||
const result: CheckResult = quiz.check(1) | ||
expect(result.isCorrect).toBe(true) | ||
expect(result.points).toBe(1) | ||
expect(result.nextQuestionId).toBeNull() | ||
}) | ||
|
||
it('should handle negative answer index', () => { | ||
const quiz = new Quiz(quizStructure) | ||
const result: CheckResult = quiz.check(-1) | ||
expect(result.isCorrect).toBe(false) | ||
expect(result.points).toBe(0) | ||
expect(result.nextQuestionId).toBe(1) | ||
}) | ||
|
||
it('should return false for check if no current question', () => { | ||
const emptyQuizStructure: QuizStructure = { questions: [] } | ||
expect(() => new Quiz(emptyQuizStructure)).toThrow() | ||
}) | ||
|
||
it('should handle quiz with maximum points', () => { | ||
const maxPointsQuizStructure: QuizStructure = { | ||
questions: [generateQuestion(0), generateQuestion(1), generateQuestion(2), generateQuestion(1)], | ||
} | ||
const quiz = new Quiz(maxPointsQuizStructure) | ||
quiz.check(0) | ||
quiz.currentQuestionIndex++ | ||
quiz.check(1) | ||
quiz.currentQuestionIndex++ | ||
quiz.check(2) | ||
quiz.currentQuestionIndex++ | ||
const result: CheckResult = quiz.check(1) | ||
expect(result.points).toBe(1) | ||
expect(result.isCorrect).toBe(true) | ||
expect(result.nextQuestionId).toBeNull() | ||
}) | ||
}) |
Oops, something went wrong.