Home / IELTS / Writing / IELTS Writing Mock Test

IELTS Writing Mock Test

IELTS Writing Mock Test tailwind.config = { theme: { extend: { colors: { primary: '#0078FF', secondary: '#F0F2F5', dark: '#181818', } } }, darkMode: 'class' } @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); body { font-family: 'Inter', sans-serif; } .test-content::-webkit-scrollbar { width: 8px; } .test-content::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.05); border-radius: 8px; } .test-content::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.1); border-radius: 8px; } .test-content::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, 0.2); } .response-textarea { resize: vertical; min-height: 200px; border: 1px solid #e2e8f0; border-radius: 0.375rem; padding: 0.75rem; width: 100%; font-size: 1rem; line-height: 1.5; } .response-textarea:focus { outline: none; border-color: #0078FF; box-shadow: 0 0 0 3px rgba(0, 120, 255, 0.2); } .dark .response-textarea { background-color: #2d3748; color: #e2e8f0; border-color: #4a5568; } .dark .response-textarea:focus { border-color: #0078FF; box-shadow: 0 0 0 3px rgba(0, 120, 255, 0.2); } .evaluation-container { max-height: 0; overflow: hidden; transition: max-height 0.5s ease-out; } .evaluation-container.show { max-height: 2000px; } #testSelector { margin: 0px; padding: 15px; padding-right: 50px; background-color: white; color: #0078FF; border: none; font-size: 20px; border-radius: 200px; cursor: pointer; outline: none; } .dark #testSelector { background-color: #2d3748; color: #e2e8f0; } .chart-container { max-width: 100%; overflow-x: auto; } /* Word count badge styles */ .word-count { position: absolute; bottom: 10px; right: 10px; background-color: rgba(0, 120, 255, 0.1); color: #0078FF; padding: 2px 8px; border-radius: 12px; font-size: 12px; pointer-events: none; } .dark .word-count { background-color: rgba(0, 120, 255, 0.2); color: #a4caff; } /* Textarea container for positioning word count */ .textarea-container { position: relative; } /* Loading spinner */ .spinner { border: 3px solid rgba(0, 120, 255, 0.1); border-radius: 50%; border-top: 3px solid #0078FF; width: 24px; height: 24px; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
IELTS Writing Mock Test
Set 1: Environment & Resources Set 2: Education & Technology Set 3: Health & Lifestyle Set 4: Society & Culture Set 5: Work & Career Set 6: Cities & Architecture Set 7: Economics & Business Set 8: Crime & Justice Set 9: Media & Communication Set 10: Science & Research
60:00
// Initialize dark mode if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.classList.add('dark'); } window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { if (event.matches) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }); // Test data with 10 sets of IELTS Writing tasks const testQuestionSets = [ // Set 1: Environment & Resources { name: "Environment & Resources", tasks: [ { type: "Task 1", instruction: "The graph below shows global carbon dioxide emissions from 1970 to 2020 by region. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "lineChart", chartData: { title: "Global CO₂ Emissions by Region (1970-2020)", xAxis: ["1970", "1980", "1990", "2000", "2010", "2020"], yAxisTitle: "CO₂ Emissions (Billion Tonnes)", series: [ { name: "North America", data: [6.0, 6.5, 6.8, 7.5, 7.0, 5.8], color: "#1f77b4" }, { name: "Europe", data: [5.7, 5.5, 5.1, 5.0, 4.8, 4.0], color: "#ff7f0e" }, { name: "Asia", data: [2.1, 3.2, 5.0, 7.0, 11.2, 14.5], color: "#2ca02c" }, { name: "Africa", data: [0.5, 0.8, 1.0, 1.2, 1.5, 1.8], color: "#d62728" }, { name: "South America", data: [0.4, 0.7, 0.9, 1.3, 1.6, 1.7], color: "#9467bd" } ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "Some people believe that protecting the environment should be the top priority for governments, even if it means slower economic development. Others argue that economic growth should be prioritized. Discuss both these views and give your own opinion.", wordCount: 250, timeLimit: 40 } ] }, // Set 2: Education & Technology { name: "Education & Technology", tasks: [ { type: "Task 1", instruction: "The table below shows the percentage of university students who used different types of digital learning resources in 2010 and 2020. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "table", chartData: { title: "University Students' Use of Digital Learning Resources (%)", headers: ["Resource Type", "2010", "2020", "Change"], rows: [ ["Digital textbooks", "32%", "78%", "+46%"], ["Online video lectures", "24%", "89%", "+65%"], ["Mobile learning apps", "11%", "73%", "+62%"], ["Online discussion forums", "38%", "67%", "+29%"], ["Virtual simulations", "14%", "51%", "+37%"], ["AI-based tutoring tools", "5%", "42%", "+37%"] ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "In many countries, online education is becoming increasingly popular. Some people believe traditional classroom learning will eventually be replaced by online learning. To what extent do you agree or disagree with this view?", wordCount: 250, timeLimit: 40 } ] }, // Set 3: Health & Lifestyle { name: "Health & Lifestyle", tasks: [ { type: "Task 1", instruction: "The diagrams below show the process of how yoga practice affects the human body and mind. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "processChart", chartData: { title: "How Yoga Practice Affects Body and Mind", steps: [ { title: "Initial Practice", description: "Regular breathing exercises and physical poses", effects: ["Increased oxygen intake", "Improved flexibility", "Enhanced focus"] }, { title: "Short-term Effects", description: "After 2-4 weeks of regular practice", effects: ["Reduced stress hormones", "Better posture", "Improved sleep quality"] }, { title: "Medium-term Effects", description: "After 2-6 months of practice", effects: ["Lower blood pressure", "Increased muscle strength", "Better concentration"] }, { title: "Long-term Effects", description: "After 1+ year of consistent practice", effects: ["Structural improvements in brain", "Enhanced immune function", "Improved emotional regulation"] } ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "Some people believe that the government should be responsible for improving public health, while others think individuals should take care of their own health. Discuss both views and give your opinion.", wordCount: 250, timeLimit: 40 } ] }, // Set 4: Society & Culture { name: "Society & Culture", tasks: [ { type: "Task 1", instruction: "The charts below show the average time spent on different leisure activities by people in a European country in 1990 and 2020. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "barChart", chartData: { title: "Average Weekly Hours Spent on Leisure Activities", categories: ["Watching TV", "Social media", "Reading", "Exercise", "Socializing", "Hobbies"], series: [ { name: "1990", data: [18, 0, 7, 4, 8, 6], color: "#5470c6" }, { name: "2020", data: [12, 14, 3, 5, 5, 4], color: "#91cc75" } ], yAxisTitle: "Hours per week" }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "In many countries, traditional cultural practices are being lost as people adopt more globalized lifestyles. Is this a positive or negative development?", wordCount: 250, timeLimit: 40 } ] }, // Set 5: Work & Career { name: "Work & Career", tasks: [ { type: "Task 1", instruction: "The pie charts below show the percentages of different job types in a developed country in 2000 and 2020. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "pieChart", chartData: { title: "Distribution of Job Types (2000 vs 2020)", charts: [ { title: "2000", data: [ { name: "Manufacturing", value: 28, color: "#5470c6" }, { name: "Services", value: 42, color: "#91cc75" }, { name: "Technology", value: 12, color: "#fac858" }, { name: "Agriculture", value: 8, color: "#ee6666" }, { name: "Other", value: 10, color: "#73c0de" } ] }, { title: "2020", data: [ { name: "Manufacturing", value: 15, color: "#5470c6" }, { name: "Services", value: 48, color: "#91cc75" }, { name: "Technology", value: 26, color: "#fac858" }, { name: "Agriculture", value: 4, color: "#ee6666" }, { name: "Other", value: 7, color: "#73c0de" } ] } ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "Some people believe that having a job with a high salary is more important than job satisfaction. To what extent do you agree or disagree?", wordCount: 250, timeLimit: 40 } ] }, // Set 6: Cities & Architecture { name: "Cities & Architecture", tasks: [ { type: "Task 1", instruction: "The maps below show the development of a small town between 1980 and 2020. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "map", chartData: { title: "Town Development: 1980 vs 2020", description: "The maps show the transformation of Riverdale town over a 40-year period, with significant expansion of urban areas, new transportation infrastructure, and conversion of agricultural land to residential and commercial use." }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "As cities continue to grow, many governments are struggling to provide adequate housing for their populations. What are the causes of urban housing problems and what solutions could be implemented?", wordCount: 250, timeLimit: 40 } ] }, // Set 7: Economics & Business { name: "Economics & Business", tasks: [ { type: "Task 1", instruction: "The chart below shows the percentage of small businesses that failed within five years of starting, across different industries in a developed economy. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "horizontalBarChart", chartData: { title: "Small Business Failure Rates by Industry (Within 5 Years)", categories: ["Restaurants", "Retail", "Construction", "Technology", "Healthcare", "Professional Services", "Manufacturing"], data: [60, 53, 48, 63, 38, 41, 49], xAxisTitle: "Failure Rate (%)" }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "Some people believe that governments should provide financial support to artists such as painters, musicians and poets. Others believe that artists should be funded by alternative sources. Discuss both views and give your opinion.", wordCount: 250, timeLimit: 40 } ] }, // Set 8: Crime & Justice { name: "Crime & Justice", tasks: [ { type: "Task 1", instruction: "The graphs below show the number of reported crimes per 100,000 people in a developed country from 2000 to 2020, divided into different categories. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "lineChart", chartData: { title: "Crime Rates per 100,000 Population (2000-2020)", xAxis: ["2000", "2005", "2010", "2015", "2020"], yAxisTitle: "Number of crimes per 100,000 population", series: [ { name: "Violent crimes", data: [450, 425, 410, 380, 360], color: "#c23531" }, { name: "Property crimes", data: [3200, 2800, 2400, 2100, 1800], color: "#2f4554" }, { name: "Fraud", data: [200, 280, 420, 650, 820], color: "#61a0a8" }, { name: "Cyber crimes", data: [50, 120, 280, 560, 910], color: "#d48265" } ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "Some people believe that the best way to reduce crime is to give longer prison sentences. Others, however, believe there are better alternative ways of reducing crime. Discuss both these views and give your own opinion.", wordCount: 250, timeLimit: 40 } ] }, // Set 9: Media & Communication { name: "Media & Communication", tasks: [ { type: "Task 1", instruction: "The diagrams below show how people received news in 1990 and 2020. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "pieChart", chartData: { title: "News Consumption Sources (1990 vs 2020)", charts: [ { title: "1990", data: [ { name: "Television", value: 45, color: "#5470c6" }, { name: "Newspapers", value: 35, color: "#91cc75" }, { name: "Radio", value: 18, color: "#fac858" }, { name: "Internet", value: 2, color: "#ee6666" } ] }, { title: "2020", data: [ { name: "Television", value: 26, color: "#5470c6" }, { name: "Newspapers", value: 8, color: "#91cc75" }, { name: "Radio", value: 6, color: "#fac858" }, { name: "Internet", value: 45, color: "#ee6666" }, { name: "Social Media", value: 15, color: "#73c0de" } ] } ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "The Internet has transformed the way information is shared and consumed. While some argue this has improved the world, others feel these changes have had negative consequences. Discuss both views and give your opinion.", wordCount: 250, timeLimit: 40 } ] }, // Set 10: Science & Research { name: "Science & Research", tasks: [ { type: "Task 1", instruction: "The diagram below shows the process of how a new pharmaceutical drug is developed and approved. Summarize the information by selecting and reporting the main features, and make comparisons where relevant.", chartType: "processChart", chartData: { title: "Pharmaceutical Drug Development Process", steps: [ { title: "Discovery & Research", description: "3-6 years", effects: ["Identify target molecules", "Develop candidate compounds", "Test in laboratory"] }, { title: "Preclinical Testing", description: "1-3 years", effects: ["Laboratory tests", "Animal testing", "Safety assessment"] }, { title: "Clinical Trials", description: "6-7 years", effects: ["Phase I: 20-100 healthy volunteers", "Phase II: 100-500 patient volunteers", "Phase III: 1,000-5,000 patient volunteers"] }, { title: "FDA Review & Approval", description: "1-2 years", effects: ["New Drug Application (NDA) review", "Advisory committee review", "FDA decision"] }, { title: "Post-Marketing Surveillance", description: "Ongoing", effects: ["Monitor for side effects", "Additional studies", "Potential label changes"] } ] }, wordCount: 150, timeLimit: 20 }, { type: "Task 2", instruction: "Scientific research should be carried out and controlled by governments rather than private companies. To what extent do you agree or disagree with this opinion?", wordCount: 250, timeLimit: 40 } ] } ]; let currentChatId = null; let currentSetIndex = 0; let currentTaskIndex = 0; let timerInterval; let timeRemaining = 3600; // 60 minutes in seconds let testInProgress = false; let responses = { task1: "", task2: "" }; // DOM Elements const testContent = document.getElementById('testContent'); const startButton = document.getElementById('startButton'); const endButton = document.getElementById('endButton'); const timerDisplay = document.getElementById('timerDisplay'); const testSelector = document.getElementById('testSelector'); // Update question set when selector changes testSelector.addEventListener('change', function () { currentSetIndex = parseInt(this.value); resetTest(); displayWelcomeMessage(); }); // Format time for display (mm:ss) function formatTime(seconds) { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`; } // Update timer display function updateTimer() { if (timeRemaining <= 0) { clearInterval(timerInterval); endTest(true); return; } timeRemaining--; timerDisplay.textContent = formatTime(timeRemaining); // Visual indicator when time is running low if (timeRemaining < 300) { // Less than 5 minutes timerDisplay.classList.add('text-red-600', 'dark:text-red-400'); if (timeRemaining % 60 === 0) { // Every minute in last 5 minutes showTimeWarning(timeRemaining / 60); } } } // Show time warning function showTimeWarning(minutes) { const warningMsg = document.createElement('div'); warningMsg.className = 'fixed top-4 right-4 bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 px-4 py-2 rounded-lg shadow-lg transition-all duration-500 opacity-0'; warningMsg.innerHTML = `Time Alert: ${minutes} minute${minutes > 1 ? 's' : ''} remaining!`; document.body.appendChild(warningMsg); // Fade in setTimeout(() => { warningMsg.classList.replace('opacity-0', 'opacity-100'); }, 100); // Fade out and remove setTimeout(() => { warningMsg.classList.replace('opacity-100', 'opacity-0'); setTimeout(() => { document.body.removeChild(warningMsg); }, 500); }, 5000); } // Reset test to beginning function resetTest() { currentTaskIndex = 0; testInProgress = false; timeRemaining = 3600; // Reset to 60 minutes timerDisplay.textContent = formatTime(timeRemaining); timerDisplay.classList.remove('text-red-600', 'dark:text-red-400'); if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } responses = { task1: "", task2: "" }; startButton.textContent = 'Start'; startButton.classList.remove('bg-red-500', 'hover:bg-red-600'); startButton.classList.add('bg-primary', 'hover:bg-blue-600'); } // Start test function startTest() { testInProgress = true; startButton.textContent = 'Pause'; // Initialize timer if (!timerInterval) { timerInterval = setInterval(updateTimer, 1000); } // Display tasks displayTasks(); } // Pause test function pauseTest() { testInProgress = false; startButton.textContent = 'Resume'; if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } } // End test function endTest(timeExpired = false) { testInProgress = false; if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } // Save any unsaved responses const task1Textarea = document.getElementById('task1-response'); const task2Textarea = document.getElementById('task2-response'); if (task1Textarea) { responses.task1 = task1Textarea.value; } if (task2Textarea) { responses.task2 = task2Textarea.value; } // Show completed message and results let completionMessage = timeExpired ? 'Time\'s up! Your test has been submitted.' : 'You\'ve completed the test!'; testContent.innerHTML = `

${completionMessage}

You can view your responses below and get AI evaluation of your writing.

Task 1 Response

${responses.task1 ? formatResponseText(responses.task1) : 'No response provided'}

Task 2 Response

${responses.task2 ? formatResponseText(responses.task2) : 'No response provided'}
`; // Add event listeners to evaluation buttons document.querySelectorAll('.evaluate-button').forEach(button => { button.addEventListener('click', function () { const task = this.getAttribute('data-task'); const responseText = responses[task]; if (responseText.trim() === '') { alert('No response to evaluate.'); return; } evaluateWriting(task, responseText); }); }); // Add event listeners to rewrite buttons document.querySelectorAll('.rewrite-button').forEach(button => { button.addEventListener('click', function () { const task = this.getAttribute('data-task'); const responseText = responses[task]; if (responseText.trim() === '') { alert('No response to rewrite.'); return; } rewriteResponse(task, responseText); }); }); // Add event listener to new test button document.getElementById('newTestButton').addEventListener('click', function () { resetTest(); displayWelcomeMessage(); }); // Reset button state startButton.textContent = 'Start'; startButton.classList.remove('bg-red-500', 'hover:bg-red-600'); startButton.classList.add('bg-primary', 'hover:bg-blue-600'); } // Format response text for display (preserving paragraphs) function formatResponseText(text) { if (!text) return 'No response provided'; // Replace newlines with paragraph tags return text.split('\n') .filter(para => para.trim() !== '') .map(para => `

${para}

`) .join(''); } // Display writing tasks function displayTasks() { const currentSet = testQuestionSets[currentSetIndex]; const task1 = currentSet.tasks[0]; const task2 = currentSet.tasks[1]; let task1ChartHTML = ''; // Generate appropriate chart/diagram based on type switch (task1.chartType) { case 'lineChart': task1ChartHTML = generateLineChart(task1.chartData); break; case 'barChart': task1ChartHTML = generateBarChart(task1.chartData); break; case 'horizontalBarChart': task1ChartHTML = generateHorizontalBarChart(task1.chartData); break; case 'pieChart': task1ChartHTML = generatePieChart(task1.chartData); break; case 'table': task1ChartHTML = generateTable(task1.chartData); break; case 'processChart': task1ChartHTML = generateProcessChart(task1.chartData); break; case 'map': task1ChartHTML = generateMap(task1.chartData); break; default: task1ChartHTML = '

Chart type not supported

'; } testContent.innerHTML = `

Writing Task 1

Spend about 20 minutes on this task. Write at least 150 words.

${task1.instruction}

${task1ChartHTML}
0 words

Writing Task 2

Spend about 40 minutes on this task. Write at least 250 words.

${task2.instruction}

0 words
`; // Add word count functionality to task 1 textarea const task1Textarea = document.getElementById('task1-response'); const wordCountTask1 = document.getElementById('word-count-task1'); updateWordCount(task1Textarea, wordCountTask1); task1Textarea.addEventListener('input', function () { updateWordCount(this, wordCountTask1); // Save response as user types responses.task1 = this.value; }); // Add word count functionality to task 2 textarea const task2Textarea = document.getElementById('task2-response'); const wordCountTask2 = document.getElementById('word-count-task2'); updateWordCount(task2Textarea, wordCountTask2); task2Textarea.addEventListener('input', function () { updateWordCount(this, wordCountTask2); // Save response as user types responses.task2 = this.value; }); } // Update word count function updateWordCount(textarea, wordCountElement) { const text = textarea.value.trim(); const wordCount = text ? text.split(/\s+/).length : 0; wordCountElement.textContent = `${wordCount} words`; // Update color based on word count requirement const minWords = textarea.id.includes('task1') ? 150 : 250; if (wordCount s.data)) * 1.1; const yScale = (height - padding * 2) / maxValue; let svg = ` ${data.title} ${data.yAxisTitle} ${data.xAxis.map((label, i) => ` ${label} `).join('')} ${Array.from({ length: 6 }, (_, i) => { const value = Math.round(maxValue * i / 5); return ` ${value} `; }).join('')} ${Array.from({ length: 6 }, (_, i) => { const value = Math.round(maxValue * i / 5); return ` `; }).join('')} ${data.series.map((series, seriesIndex) => { // Line const points = series.data.map((value, i) => `${padding + i * xScale},${height - padding - value * yScale}`).join(' '); return ` ${series.data.map((value, i) => ` `).join('')} `; }).join('')} ${data.series.map((series, i) => ` ${series.name} `).join('')} `; return svg; } function generateBarChart(data) { const width = 600; const height = 400; const padding = 60; const barWidth = (width - padding * 2) / (data.categories.length * (data.series.length + 1)); const maxValue = Math.max(...data.series.flatMap(s => s.data)) * 1.1; const yScale = (height - padding * 2) / maxValue; let svg = ` ${data.title} ${data.yAxisTitle} ${data.categories.map((label, i) => ` ${label} `).join('')} ${Array.from({ length: 6 }, (_, i) => { const value = Math.round(maxValue * i / 5); return ` ${value} `; }).join('')} ${Array.from({ length: 6 }, (_, i) => { const value = Math.round(maxValue * i / 5); return ` `; }).join('')} ${data.series.map((series, seriesIndex) => series.data.map((value, i) => ` `).join('') ).join('')} ${data.series.map((series, i) => ` ${series.name} `).join('')} `; return svg; } function generateHorizontalBarChart(data) { const width = 600; const height = 400; const padding = 60; const barHeight = (height - padding * 2) / data.categories.length; const maxValue = Math.max(...data.data) * 1.1; const xScale = (width - padding * 2) / maxValue; let svg = ` ${data.title} ${data.xAxisTitle} ${data.categories.map((label, i) => ` ${label} `).join('')} ${Array.from({ length: 6 }, (_, i) => { const value = Math.round(maxValue * i / 5); return ` ${value}% `; }).join('')} ${Array.from({ length: 6 }, (_, i) => { const value = Math.round(maxValue * i / 5); return ` `; }).join('')} ${data.data.map((value, i) => ` ${value}% `).join('')} `; return svg; } function generatePieChart(data) { const width = 300; const height = 300; const radius = 100; const centerX = width / 2; const centerY = height / 2; // Multiple pie charts if (data.charts) { let svg = `

${data.title}

`; data.charts.forEach(chart => { let startAngle = 0; let paths = ''; let legends = ''; chart.data.forEach((slice, i) => { const slicePercentage = slice.value / chart.data.reduce((sum, s) => sum + s.value, 0); const endAngle = startAngle + slicePercentage * 2 * Math.PI; const startX = centerX + radius * Math.cos(startAngle); const startY = centerY + radius * Math.sin(startAngle); const endX = centerX + radius * Math.cos(endAngle); const endY = centerY + radius * Math.sin(endAngle); const largeArcFlag = slicePercentage > 0.5 ? 1 : 0; paths += ` `; // Add label at the midpoint of the arc const midAngle = startAngle + (endAngle - startAngle) / 2; const labelRadius = radius * 0.7; // Position label at 70% of radius const labelX = centerX + labelRadius * Math.cos(midAngle); const labelY = centerY + labelRadius * Math.sin(midAngle); if (slicePercentage > 0.05) { // Only add label if slice is big enough paths += ` ${slice.value}% `; } // Add to legend legends += ` ${slice.name} (${slice.value}%) `; startAngle = endAngle; }); svg += `

${chart.title}

${paths} ${legends}
`; }); svg += `
`; return svg; } // Single pie chart let startAngle = 0; let paths = ''; let legends = ''; data.slices.forEach((slice, i) => { const slicePercentage = slice.value / data.slices.reduce((sum, s) => sum + s.value, 0); const endAngle = startAngle + slicePercentage * 2 * Math.PI; // Remaining code similar to multiple charts scenario // ... }); return ` ${data.title} ${paths} ${legends} `; } function generateTable(data) { return `
${data.headers.map(header => ` `).join('')} ${data.rows.map((row, i) => ` ${row.map((cell, j) => ` `).join('')} `).join('')}
${data.title}
${header}
${cell}
`; } function generateProcessChart(data) { return `

${data.title}

${data.steps.map((step, i) => `
${i + 1}

${step.title}

${step.description}

    ${step.effects.map(effect => `
  • ${effect}
  • `).join('')}
${i < data.steps.length - 1 ? `
` : ''}
`).join('')}
`; } function generateMap(data) { // Since we can't include external images, let's create a schematic map representation using SVG return `

${data.title}

1980

Town Center Town Center Residential Agricultural

2020

Town Center Town Center Residential Industrial Commercial Agricultural Park

${data.description}

`; } // Evaluate writing response using AI async function evaluateWriting(taskType, responseText) { const evaluationContainer = document.getElementById(`${taskType}-evaluation`); // Add a loading state and show container evaluationContainer.innerHTML = `

Evaluating your writing...

`; evaluationContainer.classList.add('show'); // Get task information const currentSet = testQuestionSets[currentSetIndex]; const task = taskType === 'task1' ? currentSet.tasks[0] : currentSet.tasks[1]; // Prepare prompt for AI const systemPrompt = ` You are an IELTS writing examiner with extensive experience. Evaluate the candidate's response for an IELTS ${task.type}. The task was: "${task.instruction}" Provide band scores (0-9, can use half bands like 6.5) and detailed comments for each of these criteria: 1. Task Achievement/Response: How well the candidate addresses all parts of the task with a fully developed position. 2. Coherence and Cohesion: How well the information and ideas are organized, using paragraphs and cohesive devices. 3. Lexical Resource: The range and precision of vocabulary used. 4. Grammatical Range and Accuracy: The range and accuracy of grammar used. 1. Task Achievement (TA) Task 1 (Academic Reports): Focus on covering all parts of the data description, identifying trends, key comparisons, and accurate data representation. Task 2 (Essays): Evaluate argument development, task relevance, and balance. Band Numerical Parameters for Task Achievement Band 9 - Covers 100% of the task, with at least 2 trends/key features for Task 1 or 3 fully developed arguments for Task 2.
- Includes at least 3 examples/details.
- Word count met or exceeded: 170+ (Task 1) / 270+ (Task 2).
- No irrelevant information or repetition.
- Clear focus with no overgeneralization. Band 8 - Covers 90–95% of the task, with 2 trends or 2–3 arguments, though some may lack depth.
- Includes at least 2 examples/details.
- Word count is met: 150–170 (Task 1) / 250–270 (Task 2).
- Minor irrelevance or redundancy. Band 7 - Covers 80–85% of the task, with 1–2 key trends or 2 arguments, but some omissions.
- At least 1 example/detail provided.
- Word count is minimally met: 150 (Task 1) / 250 (Task 2).
- Some parts may lack relevance or clarity. Band 6 - Covers 60–70% of the task, missing key trends (Task 1) or important arguments (Task 2).
- Limited or no examples provided.
- Word count barely met or below requirements.
- Significant irrelevance or repetition. 2. Coherence and Cohesion (CC) Task 1: Logical organization of the data description. Task 2: Well-structured essays with clear ideas and seamless transitions. Band Numerical Parameters for Coherence and Cohesion Band 9 - At least 4 paragraphs with clear topic sentences.
- Uses 7+ cohesive devices (e.g., "however," "in contrast," "therefore").
- Includes 5+ references/pronouns (e.g., "this," "these," "it") to avoid repetition.
- Ideas flow seamlessly with no abrupt transitions. Band 8 - At least 4 paragraphs with mostly clear topic sentences.
- Uses 5–6 cohesive devices, though slightly repetitive.
- Includes 3–4 references/pronouns.
- Logical flow with minor disruptions in transitions. Band 7 - At least 4 paragraphs, but topic sentences are unclear or repetitive.
- Uses 3–5 cohesive devices, but variety is limited.
- Includes 2–3 references/pronouns.
- Some abrupt transitions or unclear connections. Band 6 - Poor paragraphing (e.g., less than 4 paragraphs or disorganized structure).
- Uses 1–2 cohesive devices, often incorrectly.
- Limited or no referencing, leading to repetition.
- Ideas often lack logical flow. 3. Lexical Resource (LR) Task 1: Range of vocabulary for describing trends, comparisons, and data. Task 2: Use of advanced vocabulary, synonyms, and topic-specific words. Band Numerical Parameters for Lexical Resource Band 9 - Includes 5+ advanced words (e.g., "mitigate," "exponential," "sustainability").
- At least 3 topic-specific words/collocations (e.g., "pose a challenge," "carbon emissions").
- At least 3 paraphrases of key task terms.
- No spelling or word-choice errors. Band 8 - Includes 3–4 advanced words, used accurately.
- At least 2 topic-specific words/collocations.
- At least 2 attempts to paraphrase task language.
- 1–2 minor errors in spelling or word choice. Band 7 - Includes 2–3 advanced words, though one may be used incorrectly.
- At least 1–2 topic-specific words/collocations, but accuracy may vary.
- At least 1 attempt to paraphrase task language.
- 2–3 minor errors in spelling or word choice. Band 6 - Limited vocabulary, with no advanced words.
- Few or no topic-specific words.
- Repeated use of the same phrases from the task prompt.
- Frequent spelling or word-choice errors. 4. Grammatical Range and Accuracy (GRA) Task 1: Sentence variety to describe trends, comparisons, and percentages. Task 2: Complex sentence structures to develop arguments. Band Numerical Parameters for Grammatical Range and Accuracy Band 9 - Includes 5+ complex sentences (e.g., conditionals, relative clauses, comparisons).
- Uses at least 3 different tenses accurately.
- At least 90% of sentences are error-free.
- Includes 2+ advanced structures (e.g., passive voice, inversion). Band 8 - Includes 4–5 complex sentences.
- Uses at least 2 different tenses accurately.
- At least 75–80% of sentences are error-free.
- At least 1 advanced structure is used. Band 7 - Includes 3–4 complex sentences, but some may have errors.
- Uses at least 2 tenses, but with minor errors.
- At least 60–70% of sentences are error-free.
- Basic sentence variety, with occasional repetition. Band 6 - Limited use of complex sentences (1–2 attempts, often incorrect).
- Frequent tense errors (e.g., inconsistent past/present usage).
- At least 50% of sentences have errors.
- Repetitive sentence structures. Then provide an overall band score and 2-3 sentences of constructive feedback on how to improve. Only return a JSON object with this structure: { "taskAchievement": {"score": 0.0, "comment": ""}, "coherence": {"score": 0.0, "comment": ""}, "lexical": {"score": 0.0, "comment": ""}, "grammar": {"score": 0.0, "comment": ""}, "overall": 0.0, "feedback": "" } `; try { // Start a new chat session if we don't have one if (!currentChatId) { const newChatResponse = await fetch('https://server-ef04.onrender.com/api/chat/new', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const newChatData = await newChatResponse.json(); if (newChatData.success) { currentChatId = newChatData.chatId; } else { throw new Error(newChatData.error || 'Failed to start chat session'); } } // Send the evaluation request const response = await fetch('https://server-ef04.onrender.com/api/chat/message', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ chatId: currentChatId, message: `System: ${systemPrompt}\n\nCandidate's response for IELTS ${task.type}: ${responseText}` }) }); const data = await response.json(); if (!data || !data.success || !data.response) { throw new Error(data.error || "Invalid response from API"); } // Try to parse the response as JSON let evaluation; // Extract JSON from message content const jsonMatch = data.response.match(/{[\s\S]*}/); if (jsonMatch) { evaluation = JSON.parse(jsonMatch[0]); } else { throw new Error("Could not parse evaluation JSON from response"); } displayEvaluation(evaluation, evaluationContainer); } catch (error) { console.error('Error evaluating writing:', error); evaluationContainer.innerHTML = `

Error evaluating your writing: ${error.message || "Unknown error"}. Please try again.

`; } } // Display evaluation results function displayEvaluation(evaluation, container) { // Map scores to colors function getScoreColor(score) { if (score >= 8) return 'text-green-600 dark:text-green-400'; if (score >= 6.5) return 'text-blue-600 dark:text-blue-400'; if (score >= 5) return 'text-yellow-600 dark:text-yellow-400'; return 'text-red-600 dark:text-red-400'; } let evaluation_overall = (evaluation.taskAchievement.score + evaluation.coherence.score + evaluation.lexical.score + evaluation.grammar.score)/4; console.log(evaluation_overall); evaluation_overall = Math.round(evaluation_overall / 0.5) * 0.5; console.log(evaluation_overall); container.innerHTML = `

IELTS Writing Evaluation

Task Achievement

${evaluation.taskAchievement.score}

${evaluation.taskAchievement.comment}

Coherence & Cohesion

${evaluation.coherence.score}

${evaluation.coherence.comment}

Lexical Resource

${evaluation.lexical.score}

${evaluation.lexical.comment}

Grammatical Range & Accuracy

${evaluation.grammar.score}

${evaluation.grammar.comment}

Overall Band Score

${evaluation_overall}

${evaluation.feedback}

`; } // Rewrite response using AI async function rewriteResponse(taskType, responseText) { const evaluationContainer = document.getElementById(`${taskType}-evaluation`); // Add a loading state and show container evaluationContainer.innerHTML = `

Improving your writing...

`; evaluationContainer.classList.add('show'); // Get task information const currentSet = testQuestionSets[currentSetIndex]; const task = taskType === 'task1' ? currentSet.tasks[0] : currentSet.tasks[1]; try { // Prepare prompt for AI const systemPrompt = ` You are an IELTS writing expert who helps candidates improve their writing. Please improve the following IELTS ${task.type} response, making it more coherent, sophisticated, and accurate. Ensure it fully addresses the task: "${task.instruction}". Keep the same core ideas but enhance the vocabulary, grammar, and organization to achieve a band 8+ level. Only return the improved response directly, with no explanations or comments. `; // Start a new chat session if we don't have one if (!currentChatId) { const newChatResponse = await fetch('https://server-ef04.onrender.com/api/chat/new', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const newChatData = await newChatResponse.json(); if (newChatData.success) { currentChatId = newChatData.chatId; } else { throw new Error(newChatData.error || 'Failed to start chat session'); } } // Send the rewrite request const response = await fetch('https://server-ef04.onrender.com/api/chat/message', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ chatId: currentChatId, message: `System: ${systemPrompt}\n\nOriginal response: ${responseText}` }) }); const data = await response.json(); if (!data || !data.success || !data.response) { throw new Error(data.error || "Invalid response from API"); } const improvedText = data.response; evaluationContainer.innerHTML = `

Improved Version

${formatResponseText(improvedText)}
`; // Add event listeners to buttons evaluationContainer.querySelector('.use-improved-version').addEventListener('click', function () { responses[taskType] = improvedText; evaluationContainer.innerHTML = `

The improved version has been saved as your response.

`; // Update the response display const responseDisplay = document.querySelector(`.bg-white.dark\\:bg-gray-800:nth-of-type(${taskType === 'task1' ? 1 : 2}) .bg-gray-50.dark\\:bg-gray-700`); if (responseDisplay) { responseDisplay.innerHTML = formatResponseText(improvedText); } // After a delay, hide the message setTimeout(() => { evaluationContainer.classList.remove('show'); }, 3000); }); evaluationContainer.querySelector('.close-improved-version').addEventListener('click', function () { evaluationContainer.classList.remove('show'); }); } catch (error) { console.error('Error rewriting response:', error); evaluationContainer.innerHTML = `

Error rewriting your response: ${error.message || "Unknown error"}. Please try again.

`; } } // Display welcome message function displayWelcomeMessage() { testContent.innerHTML = `

You've selected: ${testQuestionSets[currentSetIndex].name}

This test contains two writing tasks:

  • Task 1: Analyzing visual information (20 minutes, 150+ words)
  • Task 2: Essay writing (40 minutes, 250+ words)

You will have 60 minutes to complete both tasks. Your time will begin when you click "Start".

Test Tips:

  • Read the instructions carefully
  • Plan your response before writing
  • Aim for at least 150 words for Task 1 and 250 words for Task 2
  • Leave a few minutes at the end to review your work
  • You can end the test early by clicking the "End" button
  • After completing the test, you can get AI feedback on your writing

Click "Start" when you're ready to begin the test.

`; } // Event listeners startButton.addEventListener('click', function () { if (testInProgress) { pauseTest(); } else { startTest(); } }); endButton.addEventListener('click', function () { if (confirm('Are you sure you want to end the test? This will submit your current responses.')) { endTest(); } }); // Initialize with welcome message window.addEventListener('load', function () { displayWelcomeMessage(); });

Leave a Reply

Your email address will not be published. Required fields are marked *